diff --git a/src/assets/lexicon_icon.js b/src/assets/lexicon_icon.js index 1247bcfbdf8386aea0a1163e2cf950fddb466a1e..fa5852f7f7fc4b8b445bfa13c54d910626c0c585 100644 --- a/src/assets/lexicon_icon.js +++ b/src/assets/lexicon_icon.js @@ -1,6 +1,5 @@ - /** - * Récupère les couleurs des lexiques de l’utilisateur. + * Récupère les couleurs des lexiques de l'utilisateur. * Chaque lexique est censé posséder une propriété "rGB" contenant la couleur, par exemple "65, 148, 84". * * @param {string} authToken - Le token d'authentification. @@ -13,13 +12,13 @@ async function getLexiconsColors(authToken) { const lexicons = await callApi(url, authToken); const colors = {}; lexicons.forEach(lexicon => { - colors[lexicon.id] = lexicon.rGB; + colors[lexicon.id] = lexicon.rGB; // Associe l'ID du lexique à sa couleur rGB }); - log("✅ Couleurs des lexiques récupérées :", colors); - return colors; + log("Couleurs des lexiques récupérées :", colors); + return colors; // Retourne l'objet contenant les couleurs des lexiques } catch (error) { - log("⌠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 {}; } } @@ -31,41 +30,9 @@ async function getLexiconsColors(authToken) { */ function convertColor(rgbString) { const parts = rgbString.split(',').map(part => parseInt(part.trim(), 10)); - // if (parts.length !== 3 || parts.some(isNaN)) { - // return generateRandomColor(); - // } - return "#" + parts.map(n => n.toString(16).padStart(2, '0')).join(''); + return "#" + parts.map(n => n.toString(16).padStart(2, '0')).join(''); // Convertit en hexadécimal } -/** - * Sélectionne aléatoirement une couleur dans une palette prédéfinie. - * @returns {string} Une couleur au format hexadécimal ou HSL. - */ -// function generateRandomColor() { -// const palette = [ -// "#231942", -// "#5E548E", -// "#9F86C0", -// "#BE95C4", -// "#E0B1CB", -// "#b7094c", -// "#a01a58", -// "#892b64", -// "#723c70", -// "#5b4d7c", -// "#455e89", -// "#2e6f95", -// "#1780a1", -// "#0091ad", -// "#30343f", -// "#e4d9ff", -// "#273469", -// "#1e2749" -// ]; -// const index = Math.floor(Math.random() * palette.length); -// return palette[index]; -// } - /** * Obtient (ou crée) la couleur associée à un lexique donné en utilisant browser.storage.local. * @param {string|number} lexiconId - L'identifiant du lexique. @@ -75,7 +42,7 @@ async function getOrCreateLexiconColor(lexiconId) { // Récupère la correspondance stockée dans storage let { lexiconColors } = await browser.storage.local.get("lexiconColors"); if (!lexiconColors || forceReset) { - lexiconColors = {}; + lexiconColors = {}; // Initialise un nouvel objet si aucune couleur n'est trouvée } if (window.authToken) { try { @@ -83,19 +50,14 @@ async function getOrCreateLexiconColor(lexiconId) { // Pour chaque lexique récupéré depuis l'API, on convertit la couleur rGB en hexadécimal for (const id in apiColors) { if (Object.prototype.hasOwnProperty.call(apiColors, id)) { - lexiconColors[id] = convertColor(apiColors[id]); + lexiconColors[id] = convertColor(apiColors[id]); // Stocke la couleur convertie } } } catch (error) { - log("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 - // if (!lexiconColors[lexiconId]) { - // lexiconColors[lexiconId] = generateRandomColor(); - // await browser.storage.local.set({ lexiconColors }); - // } - return lexiconColors[String(lexiconId)]; + return lexiconColors[String(lexiconId)]; // Retourne la couleur associée au lexique } /** @@ -126,14 +88,14 @@ async function updateLexiconColors(authToken) { const colorMapping = {}; for (const id in apiColors) { if (Object.prototype.hasOwnProperty.call(apiColors, id)) { - colorMapping[id] = convertColor(apiColors[id]); + colorMapping[id] = convertColor(apiColors[id]); // Convertit et stocke la couleur } } - log("✅ Mise à jour des couleurs des lexiques :", colorMapping); - await browser.storage.local.set({ lexiconColors: colorMapping }); - return colorMapping; + log("Mise à jour des couleurs des lexiques :", colorMapping); + await browser.storage.local.set({ lexiconColors: colorMapping }); // Met à jour le stockage local + return colorMapping; // Retourne la map des couleurs } catch (error) { - log("⌠Erreur lors de la mise à jour des couleurs :", error); + log("Erreur lors de la mise à jour des couleurs :", error); return {}; } } @@ -147,6 +109,7 @@ 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. @@ -161,9 +124,9 @@ function hexToRgba(hex, opacity) { return `rgba(${r}, ${g}, ${b}, ${opacity})`; } +// Expose les fonctions globalement window.updateLexiconColors = updateLexiconColors; window.getColorForLexicon = getColorForLexicon; -// window.generateRandomColor = generateRandomColor; window.convertColor = convertColor; window.getOrCreateLexiconColor = getOrCreateLexiconColor; window.createColorCircle = createColorCircle; diff --git a/src/background/background.js b/src/background/background.js index b5eb161a92f962951b37dedcaea7ac1dee466fc8..965e1d09cb9fdb40f83357daddf29c715dfe79c3 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -1,56 +1,60 @@ // ───────────────────────────────────────────────────────────────────────────── // Variables globales // ───────────────────────────────────────────────────────────────────────────── -let isExtensionActive = true; -let areStatsActive = false; -let originalTabId = null; -let loginTabId = null; +let isExtensionActive = true; // Indique si l'extension est active +let areStatsActive = false; // Indique si le suivi des statistiques est actif +let originalTabId = null; // ID de l'onglet original +let loginTabId = null; // ID de l'onglet de connexion -const AUTH_LOGIN_URL = "https://prisms.lezinter.net/fr/login"; -const AUTH_BALEX_URL = "https://prisms.lezinter.net/fr/headquarters/balex"; +const AUTH_LOGIN_URL = "https://prisms.lezinter.net/fr/login"; // URL de connexion +const AUTH_BALEX_URL = "https://prisms.lezinter.net/fr/headquarters/balex"; // URL de redirection vers BaLex // ───────────────────────────────────────────────────────────────────────────── // Logs de démarrage et initialisation // ───────────────────────────────────────────────────────────────────────────── -log("🚀 ff2BaLex (background) chargé."); - +log("ff2BaLex (background) chargé."); browser.runtime.onInstalled.addListener((details) => { - log("🔔 Extension installée ou mise à jour. Raison :", details.reason); + log("Extension installée ou mise à jour. Raison :", details.reason); }); - browser.runtime.onStartup.addListener(() => { - log("🔄 Extension démarrée (onStartup)."); + log("Extension démarrée (onStartup)."); }); - browser.runtime.onInstalled.addListener(() => { browser.storage.local.set({ extensionActive: false }); - log("🔔 Extension installée, état initialisé à désactivé."); + log("Extension installée, état initialisé à désactivé."); }); // ───────────────────────────────────────────────────────────────────────────── // Suivi des changements dans le stockage // ───────────────────────────────────────────────────────────────────────────── browser.storage.onChanged.addListener((changes) => { + // Vérifie si l'état de l'extension a changé if (changes.extensionActive) { - isExtensionActive = changes.extensionActive.newValue; - log("✅ Extension activée :", isExtensionActive); + isExtensionActive = changes.extensionActive.newValue; // Met à jour la variable d'état de l'extension + log("Extension activée :", isExtensionActive); } + // Vérifie si l'état des statistiques a changé if (changes.statsActive) { - areStatsActive = changes.statsActive.newValue; - log("📊 Statistiques activées :", areStatsActive); + areStatsActive = changes.statsActive.newValue; // Met à jour la variable d'état des statistiques + log("Statistiques activées :", areStatsActive); } + // Rafraîchit l'interface utilisateur globale refreshAllUI(); }); browser.storage.onChanged.addListener((changes, area) => { + // Vérifie si les changements concernent le stockage local et le token d'accès if (area === "local" && changes.accessToken) { - const newToken = changes.accessToken.newValue; + const newToken = changes.accessToken.newValue; // Récupère la nouvelle valeur du token if (newToken) { + // Vérifie l'état de l'extension dans le stockage local browser.storage.local.get("extensionActive").then(({ extensionActive }) => { + // Si l'extension n'est pas active, l'active automatiquement if (!extensionActive) { log("Token ajouté, activation automatique de l'extension."); - browser.storage.local.set({ extensionActive: true }); - updateExtension(); + browser.storage.local.set({ extensionActive: true }); // Met à jour l'état de l'extension + updateExtension(); // Met à jour les fonctionnalités de l'extension + // Envoie un message pour mettre à jour l'interface utilisateur browser.runtime.sendMessage({ action: "updateUI", extensionActive: true, @@ -66,13 +70,22 @@ browser.storage.onChanged.addListener((changes, area) => { // ───────────────────────────────────────────────────────────────────────────── // Fonctions utilitaires // ───────────────────────────────────────────────────────────────────────────── +/** + * Vérifie si l'utilisateur est connecté en vérifiant le token d'accès. + * @returns {Promise<boolean>} True si l'utilisateur est connecté, sinon false. + */ async function isUserConnected() { const { accessToken } = await browser.storage.local.get("accessToken"); return !!accessToken; } +/** + * Rafraîchit l'interface utilisateur globale. + * Envoie un message pour demander le rafraîchissement de l'UI. + * @returns {Promise<void>} + */ async function refreshAllUI() { - log("🔄 Rafraîchissement global de l'UI..."); + log("Rafraîchissement global de l'UI..."); try { await browser.runtime.sendMessage({ action: "refreshUI" }); } catch (error) { @@ -87,44 +100,53 @@ browser.runtime.onConnect.addListener((port) => { if (port.name === "auth") { port.onMessage.addListener(async (message) => { if (message.action === "toggleAuth") { - log("🔄 toggleAuth reçu via port dans le background."); - const isConnected = await isUserConnected(); + log("toggleAuth reçu via port dans le background."); + const isConnected = await isUserConnected(); // Vérifie si l'utilisateur est connecté if (isConnected) { - await disconnectFromLexicalDB(); + await disconnectFromLexicalDB(); // Déconnecte l'utilisateur } else { - actuallyOpenLoginPage(); + actuallyOpenLoginPage(); // Ouvre la page de connexion } } }); } }); +/** + * Ouvre la page de connexion pour l'utilisateur. + * Mémorise l'onglet actif et crée un nouvel onglet pour la connexion. + * @returns {Promise<void>} + */ async function actuallyOpenLoginPage() { - log("🔗 Ouverture de la page de connexion."); + log("Ouverture de la page de connexion."); // Mémoriser l'onglet actif const [currentTab] = await browser.tabs.query({ active: true, currentWindow: true }); if (currentTab) { - originalTabId = currentTab.id; - log("✅ Onglet courant mémorisé, ID =", originalTabId); + originalTabId = currentTab.id; // Mémorise l'ID de l'onglet courant + log("Onglet courant mémorisé, ID =", originalTabId); } // Ouvre un nouvel onglet pour la page de connexion et l'active const loginTab = await browser.tabs.create({ url: AUTH_LOGIN_URL, active: true }); - loginTabId = loginTab.id; - log("✅ Onglet de login créé, ID =", loginTabId); - // Notifie que l'authentification est en cours + loginTabId = loginTab.id; // Mémorise l'ID de l'onglet de connexion + log("Onglet de login créé, ID =", loginTabId); browser.runtime.sendMessage({ action: "authStatusChanged", isLoggedIn: false }); } +/** + * Déconnecte l'utilisateur. + * Supprime le token d'accès et désactive l'extension. + * @returns {Promise<void>} + */ async function disconnectFromLexicalDB() { - await browser.storage.local.remove("accessToken"); - log("🔓 Token supprimé avec succès."); + await browser.storage.local.remove("accessToken"); // Supprime le token d'accès + log("Token supprimé avec succès."); - await browser.storage.local.remove("lexiconColors"); + await browser.storage.local.remove("lexiconColors"); // Supprime les couleurs du lexique // Désactivation automatique de l'extension - await browser.storage.local.set({ extensionActive: false }); - disableExtensionFeatures(); + await browser.storage.local.set({ extensionActive: false }); // Met à jour l'état de l'extension + disableExtensionFeatures(); // Désactive les fonctionnalités de l'extension browser.runtime.sendMessage({ action: "updateUI", extensionActive: false, @@ -133,38 +155,43 @@ async function disconnectFromLexicalDB() { }); setTimeout(async () => { - await refreshAllUI(); + await refreshAllUI(); // Rafraîchit l'UI après un délai }, 500); } +/** + * Sauvegarde le token d'accès dans le stockage local. + * @param {string} token - Le token à sauvegarder. + * @returns {Promise<void>} + */ async function saveToken(token) { - log("✅ Sauvegarde du token :", token); + log("Sauvegarde du token :", token); await browser.storage.local.set({ accessToken: token }); if (loginTabId) { try { - await browser.tabs.remove(loginTabId); - log("🗙 Onglet de login fermé après connexion réussie."); + await browser.tabs.remove(loginTabId); // Ferme l'onglet de connexion + log("Onglet de login fermé après connexion réussie."); } catch (err) { console.warn("Impossible de fermer l'onglet de login :", err); } - loginTabId = null; + loginTabId = null; // Réinitialise l'ID de l'onglet de connexion } if (originalTabId) { try { - await browser.tabs.update(originalTabId, { active: true }); - log("🔙 Retour sur l'onglet initial :", originalTabId); + await browser.tabs.update(originalTabId, { active: true }); // Retourne sur l'onglet initial + log("Retour sur l'onglet initial :", originalTabId); } catch (err) { console.warn("Impossible de basculer sur l'onglet initial :", err); } - originalTabId = null; + originalTabId = null; // Réinitialise l'ID de l'onglet original } // Activer automatiquement l'extension const { extensionActive } = await browser.storage.local.get("extensionActive"); if (!extensionActive) { - await browser.storage.local.set({ extensionActive: true }); - updateExtension(); + await browser.storage.local.set({ extensionActive: true }); // Met à jour l'état de l'extension + updateExtension(); // Met à jour les fonctionnalités de l'extension browser.runtime.sendMessage({ action: "updateUI", extensionActive: true, @@ -175,142 +202,54 @@ async function saveToken(token) { await refreshAllUI(); } -// ───────────────────────────────────────────────────────────────────────────── -// Gestion des messages reçus -// ───────────────────────────────────────────────────────────────────────────── -browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { - log("📩 Message reçu dans background.js :", message); - - switch (message.action) { - case "toggleAuth": { - const isConnected = await isUserConnected(); - if (isConnected) { - await disconnectFromLexicalDB(); - await browser.storage.local.remove("lexiconColors"); - } else { - actuallyOpenLoginPage(); - } - break; - } - case "getDefinitionWiki": { - if (message.selectedText && message.selectedText.trim() !== "") { - log("🌠Requête Wiktionnaire pour :", message.selectedText); - const definition = await window.fetchWiktionaryDefinition(message.selectedText.trim()); - browser.runtime.sendMessage({ - action: "fetchWiktionaryDefinitionResponse", - selectedText: message.selectedText, - definitions: [{ - source: "Wiktionnaire", - text: definition, - }], - }); - } else { - console.warn("âš ï¸ Texte sélectionné vide. Annulation de la requête."); - } - break; - } - case "checkAuthStatus": { - const connected = await isUserConnected(); - sendResponse(connected); - break; - } - case "authStatusChanged": { - log("🔄 Mise à jour de l'état d'authentification :", message.isLoggedIn); - break; - } - case "saveToken": { - if (message.token) { - await saveToken(message.token); - } else { - console.warn("âš ï¸ Aucune valeur de token reçue."); - } - break; - } - case "toggleLexiconHighlight": { - const tabs = await browser.tabs.query({active: true, currentWindow: true}); - if (tabs[0]) { - try { - await browser.scripting.executeScript({ - target: { tabId: tabs[0].id }, - files: ["src/utils/highlighting.js"] - }); - - 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; - } - - case "toggleExtension": { - const newState = message.isActive; - isExtensionActive = newState; - browser.storage.local.set({ extensionActive: isExtensionActive }); - log("État de l'extension mis à jour :", isExtensionActive); - break; - } - - default: - break; - } - return true; -}); - -// ───────────────────────────────────────────────────────────────────────────── -// Web Navigation : Injection de scripts et récupération du token -// ───────────────────────────────────────────────────────────────────────────── +/** + * Gère les événements de navigation web pour injecter des scripts et récupérer le token. + * @param {Object} details - Détails de l'événement de navigation. + * @returns {Promise<void>} + */ browser.webNavigation.onCompleted.addListener(async (details) => { const url = new URL(details.url); // Injection d'un popup d'instruction sur la page de login if (url.hostname === "prisms.lezinter.net" && url.pathname === "/fr/login") { - log("📘 Injection du popup d'instruction sur la page de login Prisms."); + log("Injection du popup d'instruction sur la page de login Prisms."); showInstructionPopup(details); } // Récupération du token sur la page /balex if (url.hostname === "prisms.lezinter.net" && url.pathname === "/fr/headquarters/balex") { - log("🟢 Page /balex détectée. Tentative de récupération du token."); + log("Page /balex détectée. Tentative de récupération du token."); try { await new Promise(resolve => setTimeout(resolve, 3000)); await browser.tabs.executeScript(details.tabId, { code: ` (function() { - log("🔠Recherche du token..."); + log("Recherche du token..."); const tokenElement = document.getElementById("accessToken") || document.getElementById("accesToken"); if (tokenElement) { const token = tokenElement.innerText.trim(); - log("🔠Token détecté :", token); + log("Token détecté :", token); browser.runtime.sendMessage({ action: "saveToken", token }); } else { - log("⌠Token introuvable."); + log("Token introuvable."); } return null; })(); ` }); } catch (error) { - log("⌠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" }] }); -// ───────────────────────────────────────────────────────────────────────────── -// Web Request : Redirection automatique vers /balex -// ───────────────────────────────────────────────────────────────────────────── +/** + * Redirige automatiquement vers /balex. + * @param {Object} details - Détails de la requête. + */ browser.webRequest.onBeforeRequest.addListener( function(details) { if (details.url === "https://prisms.lezinter.net/fr/headquarters/") { - log("🚀 Redirection automatique vers /balex."); + log("Redirection automatique vers /balex."); return { redirectUrl: AUTH_BALEX_URL }; } }, @@ -318,9 +257,10 @@ browser.webRequest.onBeforeRequest.addListener( ["blocking"] ); -// ───────────────────────────────────────────────────────────────────────────── -// Affichage d'un popup d'instruction sur /fr/login -// ───────────────────────────────────────────────────────────────────────────── +/** + * Affiche un popup d'instruction sur /fr/login. + * @param {Object} details - Détails de l'onglet. + */ function showInstructionPopup(details) { browser.tabs.executeScript(details.tabId, { code: ` @@ -370,11 +310,122 @@ function showInstructionPopup(details) { }); } +// ───────────────────────────────────────────────────────────────────────────── +// Gestion des messages reçus +// ───────────────────────────────────────────────────────────────────────────── +/** + * Écouteur de messages reçus dans le script d'arrière-plan + */ +browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { + log("[Background] Message reçu :", message); + + // Traitement des actions basées sur le message reçu + switch (message.action) { + case "toggleAuth": { + // Vérifie si l'utilisateur est connecté + const isConnected = await isUserConnected(); + if (isConnected) { + await disconnectFromLexicalDB(); + await browser.storage.local.remove("lexiconColors"); + } else { + actuallyOpenLoginPage(); + } + break; + } + case "getDefinitionWiki": { + // Vérifie si le texte sélectionné est valide + if (message.selectedText && message.selectedText.trim() !== "") { + log("Requête Wiktionnaire pour :", message.selectedText); + // Récupère la définition du texte sélectionné depuis le Wiktionnaire + const definition = await window.fetchWiktionaryDefinition(message.selectedText.trim()); + // Envoie la réponse avec la définition récupérée + browser.runtime.sendMessage({ + action: "fetchWiktionaryDefinitionResponse", + selectedText: message.selectedText, + definitions: [{ + source: "Wiktionnaire", + text: definition, + }], + }); + } else { + console.warn("âš ï¸ Texte sélectionné vide. Annulation de la requête."); + } + break; + } + case "checkAuthStatus": { + // Vérifie l'état de connexion de l'utilisateur et envoie la réponse + const connected = await isUserConnected(); + sendResponse(connected); + break; + } + case "authStatusChanged": { + // Log l'état d'authentification mis à jour + log("Mise à jour de l'état d'authentification :", message.isLoggedIn); + break; + } + case "saveToken": { + // Vérifie si un token a été reçu et le sauvegarde + if (message.token) { + await saveToken(message.token); + } else { + console.warn("âš ï¸ Aucune valeur de token reçue."); + } + break; + } + case "toggleLexiconHighlight": { + // Récupère l'onglet actif et applique le script de surlignage + const tabs = await browser.tabs.query({active: true, currentWindow: true}); + if (tabs[0]) { + try { + // Injecte le script de surlignage dans l'onglet actif + await browser.scripting.executeScript({ + target: { tabId: tabs[0].id }, + files: ["src/utils/highlighting.js"] + }); + + // Envoie un message pour activer ou désactiver le surlignage + 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 l'enregistrement du script de surlignage pour l'onglet + log("Script de surlignage enregistré pour l'onglet", sender.tab.id); + break; + } + + case "toggleExtension": { + // Met à jour l'état de l'extension en fonction du message reçu + const newState = message.isActive; + isExtensionActive = newState; + browser.storage.local.set({ extensionActive: isExtensionActive }); + log("État de l'extension mis à jour :", isExtensionActive); + break; + } + + default: + break; + } + return true; // Indique que la réponse sera envoyée de manière asynchrone +}); + // ───────────────────────────────────────────────────────────────────────────── // Initialisation du WebWorker // ───────────────────────────────────────────────────────────────────────────── let worker = null; +/** + * Initialise le WebWorker pour le traitement en arrière-plan. + * @returns {void} + */ function initWorker() { if (!worker) { log("[Background] Initialisation du WebWorker..."); @@ -390,19 +441,30 @@ function initWorker() { } } +/** + * Gère les erreurs du WebWorker. + * @param {Error} error - L'erreur. + */ function handleWorkerError(error) { log("Erreur du WebWorker :", error.message); } +/** + * Gère les messages du WebWorker. + * @param {Object} event - L'événement. + */ function handleWorkerMessage(event) { const data = event.data; log("[Background] Message du WebWorker :", data); switch (data.type) { case "pyodide-simplemma": + // Vérifie le statut de Pyodide et Simplemma if (data.status === "success") { log("[Background] Pyodide et Simplemma prêts. Mise à jour de l'état."); + // Met à jour l'état dans le stockage local browser.storage.local.set({ pyodideSimplemmaReady: true }); + // Vérifie et met à jour le suivi des statistiques checkAndUpdateTracking(); } else if (data.status === "error") { log("[Background] Erreur lors du chargement :", data.message); @@ -412,13 +474,14 @@ function handleWorkerMessage(event) { break; case "update-frequencies": log("[Background] Mise à jour des fréquences :", data.frequencies); + // Notifie tous les onglets de la mise à jour des fréquences 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 }); + // Met à jour les fréquences dans le stockage local browser.storage.local.set({ lemmaFrequencies: data.frequencies }); break; case "threshold-exceeded": log("[Background] Mots dépassant le seuil :", data.wordsAboveThreshold); + // Notifie tous les onglets que le seuil a été dépassé notifyAllTabs({ command: "threshold-exceeded", wordsAboveThreshold: data.wordsAboveThreshold }); break; default: @@ -435,7 +498,7 @@ initWorker(); // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { log("[Background] Message reçu :", message); - + // Initialise le WebWorker si ce n'est pas déjà fait if (!worker) { initWorker(); } @@ -443,15 +506,19 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { if (message.command === "toggle-stats") { log(`[Background] Statistiques ${message.isActive ? "activées" : "désactivées"}`); const { isActive } = message; + // Met à jour l'état du suivi des statistiques dans le stockage local await browser.storage.local.set({ isTrackingActive: isActive }); + // Si les statistiques sont désactivées, désactive également l'ajout automatique if (!isActive) { await browser.storage.local.set({ autoAdd: false }); } + // Vérifie et met à jour l'état du suivi des statistiques checkAndUpdateTracking(); } if (message.command === "pyodide-simplemma") { log("[Background] Demande d'initialisation de Pyodide et Simplemma..."); + // Envoie un message au WebWorker pour initialiser Pyodide et Simplemma worker.postMessage({ command: "pyodide-simplemma" }); } @@ -462,6 +529,10 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { // ───────────────────────────────────────────────────────────────────────────── // Chargement et sauvegarde des fréquences stockées // ───────────────────────────────────────────────────────────────────────────── +/** + * Charge les fréquences stockées depuis le stockage local. + * @returns {Promise<Object>} Un objet contenant les fréquences stockées. + */ async function loadStoredFrequencies() { const { storedFrequencies } = await browser.storage.local.get("storedFrequencies"); return storedFrequencies || {}; @@ -477,6 +548,11 @@ loadStoredFrequencies().then(frequencies => { // ───────────────────────────────────────────────────────────────────────────── // Statistiques : Vérification et activation/désactivation du tracking // ───────────────────────────────────────────────────────────────────────────── +/** + * Vérifie et met à jour l'état du suivi des statistiques. + * Active ou désactive le suivi en fonction des préférences. + * @returns {Promise<void>} + */ async function checkAndUpdateTracking() { const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); if (isTrackingActive && pyodideSimplemmaReady) { @@ -491,6 +567,10 @@ async function checkAndUpdateTracking() { // ───────────────────────────────────────────────────────────────────────────── // Statistiques : Gestion des onglets // ───────────────────────────────────────────────────────────────────────────── +/** + * Envoie un message à tous les onglets. + * @param {Object} message - Le message à envoyer. + */ async function notifyAllTabs(message) { browser.tabs.query({}).then((tabs) => { tabs.forEach((tab) => { @@ -504,15 +584,22 @@ async function notifyAllTabs(message) { // Statistiques : Écoute des modifications du stockage et mise à jour du tracking // ───────────────────────────────────────────────────────────────────────────── browser.storage.onChanged.addListener(async (changes, area) => { + // Vérifie si les changements concernent le stockage local et les paramètres de suivi if (area === "local" && (changes.isTrackingActive || changes.pyodideSimplemmaReady)) { + // Met à jour l'état du suivi des statistiques checkAndUpdateTracking(); } + + // Vérifie si les changements concernent les préférences d'authentification ou de configuration if (area === "local" && (changes.accessToken || changes.threshold || changes.trackedLanguages || changes.autoAdd)) { log("[Background] Mise à jour des préférences détectée."); + // Récupère les valeurs mises à jour depuis le stockage local const { accessToken, trackedLanguages, threshold, autoAdd } = await browser.storage.local.get([ "accessToken", "trackedLanguages", "threshold", "autoAdd" ]); + // Vérifie si l'utilisateur est authentifié const isAuthenticated = !!accessToken; + // Envoie un message au WebWorker pour mettre à jour les préférences worker.postMessage({ command: "update-preferences", isAuthenticated, @@ -567,6 +654,10 @@ browser.runtime.onConnect.addListener((port) => { // ───────────────────────────────────────────────────────────────────────────── let stoplistFr = []; +/** + * Charge la stoplist depuis un fichier et l'envoie au Worker. + * @returns {void} + */ function loadStoplist() { fetch(browser.runtime.getURL("stoplist_fr.txt")) .then(response => response.text()) @@ -578,6 +669,10 @@ function loadStoplist() { .catch(error => log("[Background] Erreur lors du chargement de la stoplist :", error)); } +/** + * Envoie la stoplist au Worker. + * @returns {void} + */ function sendStoplistToWorker() { log("[Background] Envoi de la stoplist au Worker..."); worker.postMessage({ command: "update-stoplist", stoplist: stoplistFr }); @@ -586,6 +681,16 @@ function sendStoplistToWorker() { browser.runtime.onStartup.addListener(loadStoplist); browser.runtime.onInstalled.addListener(loadStoplist); +// ───────────────────────────────────────────────────────────────────────────── +// Surlignage +// ───────────────────────────────────────────────────────────────────────────── +/** + * Gestion du surlignage. + * @param {string} command - La commande à exécuter. + * @param {string} lexiconId - L'identifiant du lexique. + * @param {number} tabId - L'identifiant de l'onglet. + * @returns {Promise<boolean>} - True si le surlignage a été effectué, false sinon. + */ async function handleHighlighting(command, lexiconId, tabId) { log(`🎯 Gestion du surlignage: ${command} pour le lexique ${lexiconId}`); @@ -602,10 +707,10 @@ async function handleHighlighting(command, lexiconId, tabId) { lexiconId: lexiconId }); - log("✅ Réponse du content script:", response); + log("Réponse du content script:", response); return response; } catch (error) { - log("⌠Erreur lors de la gestion du surlignage:", error); + log("Erreur lors de la gestion du surlignage:", error); return false; } } \ No newline at end of file diff --git a/src/context_menu/browser_context_menu.js b/src/context_menu/browser_context_menu.js index cd98a6f297007beac7902932d13bc43505cacf24..077c806db6c58fa6596300036803a0dbcded8b0a 100644 --- a/src/context_menu/browser_context_menu.js +++ b/src/context_menu/browser_context_menu.js @@ -1,34 +1,37 @@ -log("browser_context_menu.js chargé correctement"); - -let authToken = null; -let selectedLexicons = new Set(); +// ───────────────────────────────────────────────────────────────────────────── +// Variables globales et logs +// ───────────────────────────────────────────────────────────────────────────── +log("browser_context_menu.js chargé."); +let authToken = null; // Token d'authentification +let selectedLexicons = new Set(); // Ensemble des lexiques sélectionnés +// ───────────────────────────────────────────────────────────────────────────── +// Fonctions liées à l'authentification et au menu contextuel +// ───────────────────────────────────────────────────────────────────────────── /** - * Charge le token depuis le stockage local et le stocke dans la variable globale authToken. + * Charge le token depuis le stockage local. */ async function loadAuthToken() { try { const result = await browser.storage.local.get("accessToken"); authToken = result.accessToken; - log("🔑 Token chargé au démarrage :", authToken); + log("Token chargé au démarrage :", authToken); } catch (error) { - log("⌠Erreur lors de la récupération du token :", error); + log("Erreur lors de la récupération du token :", error); } } /** * Crée le menu contextuel en fonction de l'authentification. - * Si l'utilisateur est connecté, on ajoute un item pour la recherche et - * un menu parent pour l'ajout du mot avec des cases à cocher pour chaque lexique - * et un item de confirmation. */ async function createContextMenu() { - await browser.contextMenus.removeAll(); + await browser.contextMenus.removeAll(); // Supprime tous les éléments du menu contextuel const { extensionActive } = await browser.storage.local.get("extensionActive"); log("État de l'extension :", extensionActive); if (extensionActive) { + // Création des éléments du menu contextuel si l'extension est active browser.contextMenus.create({ id: "searchInLexicons", title: "Rechercher dans mes lexiques", @@ -59,6 +62,7 @@ async function createContextMenu() { log("âš ï¸ L'extension est désactivée, aucune option d'analyse ne sera affichée."); } + // Création de l'élément de menu pour la connexion/déconnexion browser.contextMenus.create({ id: "login", title: authToken ? "Se déconnecter de BaLex" : "Se connecter à BaLex", @@ -66,23 +70,94 @@ async function createContextMenu() { }); } - +// Chargement du token et création du menu contextuel loadAuthToken().then(createContextMenu); +// Écoute des messages pour rafraîchir l'interface utilisateur browser.runtime.onMessage.addListener((message) => { if (message.action === "refreshUI" || message.action === "updateUI") { - log("🔄 refreshUI reçu dans browser_context_menu.js"); + log("refreshUI reçu dans browser_context_menu.js"); loadAuthToken().then(createContextMenu); } }); +// Écoute des changements dans le stockage local browser.storage.onChanged.addListener((changes, area) => { if (area === "local" && changes.accessToken) { - log("🔄 Token modifié, actualisation du menu contextuel."); + log("Token modifié, actualisation du menu contextuel."); loadAuthToken().then(createContextMenu); } }); +// ───────────────────────────────────────────────────────────────────────────── +// Fonctions liées aux définitions +// ───────────────────────────────────────────────────────────────────────────── +/** + * Récupère les définitions d'un mot + * @param {string} selectedText - Le mot à rechercher + */ +async function getDefinition(selectedText) { + try { + let lexiconDefs = []; // Définitions des lexiques + if (authToken) { + lexiconDefs = await fetchLexiconDefinitions(selectedText); // Récupère les définitions des lexiques + } + const wikiDefs = await fetchWiktionaryDefinition(selectedText); // Récupère les définitions du Wiktionnaire + const allDefinitions = [...lexiconDefs, ...wikiDefs]; // Combine les définitions + log("Définitions combinées :", allDefinitions); + browser.runtime.sendMessage({ + action: "showDefinitions", + selectedText, + definitions: allDefinitions, + }); + } catch (error) { + log("Erreur lors de la recherche combinée des définitions :", error); + } +} + +/** + * Recherche si un mot est présent dans les lexiques + * @param {string} selectedText - Le mot à rechercher + */ +async function searchInLexicons(selectedText) { + try { + log("Recherche dans mes lexiques :", selectedText); + const allDefinitions = await fetchLexiconDefinitions(selectedText); // Récupère toutes les définitions + if (!allDefinitions || allDefinitions.length === 0) { + log("Aucun lexique trouvé pour ce mot."); + browser.runtime.sendMessage({ + action: "showLexiconResult", + lexicons: [], + selectedText, + }); + return; + } + const lexMap = new Map(); // Map pour stocker les résultats + for (const def of allDefinitions) { + if (def.lexiconId) { + lexMap.set(def.lexiconId, def.source); // Ajoute les définitions à la map + } + } + const foundInLexicons = []; + for (const [id, name] of lexMap.entries()) { + foundInLexicons.push({ id, name }); + } + log("Envoi du message 'showLexiconResult' avec :", foundInLexicons); + browser.runtime.sendMessage({ + action: "showLexiconResult", + lexicons: foundInLexicons, + selectedText, + }); + } catch (error) { + log("Erreur lors de la recherche dans les lexiques :", error); + browser.runtime.sendMessage({ + action: "showLexiconResult", + lexicons: [], + selectedText, + }); + } +} + // ───────────────────────────────────────────────────────────────────────────── // Gestion des clics sur le menu contextuel // ───────────────────────────────────────────────────────────────────────────── @@ -91,7 +166,7 @@ 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."); + log("Action login/déconnexion demandée."); if (authToken) { await disconnectFromLexicalDB(); } else { @@ -143,69 +218,7 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => { } await searchInLexicons(selectedText); break; - } + } log(`⌠Action inconnue : ${info.menuItemId}`); }); - -// ───────────────────────────────────────────────────────────────────────────── -// Fonctions liées aux définitions -// ───────────────────────────────────────────────────────────────────────────── -async function getDefinition(selectedText) { - try { - let lexiconDefs = []; - if (authToken) { - lexiconDefs = await fetchLexiconDefinitions(selectedText); - } - const wikiDefs = await fetchWiktionaryDefinition(selectedText); - const allDefinitions = [...lexiconDefs, ...wikiDefs]; - log("📠Définitions combinées :", allDefinitions); - browser.runtime.sendMessage({ - action: "showDefinitions", - selectedText, - definitions: allDefinitions, - }); - } catch (error) { - log("⌠Erreur lors de la recherche combinée des définitions :", error); - } -} - -async function searchInLexicons(selectedText) { - try { - log("🔎 Recherche dans mes lexiques :", selectedText); - const allDefinitions = await fetchLexiconDefinitions(selectedText); - if (!allDefinitions || allDefinitions.length === 0) { - log("⌠Aucun lexique trouvé pour ce mot."); - browser.runtime.sendMessage({ - action: "showLexiconResult", - lexicons: [], - selectedText, - }); - return; - } - const lexMap = new Map(); - for (const def of allDefinitions) { - if (def.lexiconId) { - lexMap.set(def.lexiconId, def.source); - } - } - const foundInLexicons = []; - for (const [id, name] of lexMap.entries()) { - foundInLexicons.push({ id, name }); - } - log("📩 Envoi du message 'showLexiconResult' avec :", foundInLexicons); - browser.runtime.sendMessage({ - action: "showLexiconResult", - lexicons: foundInLexicons, - selectedText, - }); - } catch (error) { - log("⌠Erreur lors de la recherche dans les lexiques :", error); - browser.runtime.sendMessage({ - action: "showLexiconResult", - lexicons: [], - selectedText, - }); - } -} - diff --git a/src/context_menu/custom_context_menu.js b/src/context_menu/custom_context_menu.js index d7feb11c1b7f548a88ace1d3ccd69b9a9045dc3c..85cad9f0cf8c1fe8d1377e45f981ee8ce9489961 100644 --- a/src/context_menu/custom_context_menu.js +++ b/src/context_menu/custom_context_menu.js @@ -1,9 +1,18 @@ -log("custom_context_menu.js chargé correctement"); - -// === Variables globales === +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Variables globales et logs +// ───────────────────────────────────────────────────────────────────────────── +log("custom_context_menu.js chargé."); const CUSTOM_CONTEXT_MENU = "customContextMenu"; -// Fonction utilitaire pour envoyer une notification via le background +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Fonctions utilitaires +// ───────────────────────────────────────────────────────────────────────────── +/** + * Envoie une notification via le background. + * @param {string} title - Le titre de la notification. + * @param {string} message - Le message de la notification. + * @param {string} iconPath - Le chemin de l'icône de la notification. + */ function sendNotification(title, message, iconPath) { browser.runtime.sendMessage({ action: "showNotification", @@ -13,23 +22,41 @@ function sendNotification(title, message, iconPath) { }); } -// Récupère le token depuis le stockage local et le stocke dans authToken +/** + * Récupère le token depuis le stockage local et le stocke dans authToken. + */ async function loadAuthToken() { try { const result = await browser.storage.local.get("accessToken"); authToken = result.accessToken || null; - log("🔑 Token chargé :", authToken); + log("Token chargé :", authToken); } catch (error) { - log("⌠Erreur lors de la récupération du token :", error); + log("Erreur lors de la récupération du token :", error); authToken = null; } } +/** + * Récupère le texte affiché dans #selectedWord. + * @returns {string} - Le mot sélectionné ou une chaîne vide. + */ +function getSelectedWord() { + const selectedWordElement = document.getElementById("selectedWord"); + return selectedWordElement ? selectedWordElement.textContent.trim() : ""; +} + +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Fonctions liées au menu contextuel +// ───────────────────────────────────────────────────────────────────────────── /** * Crée le menu contextuel personnalisé (customContextMenu) s'il n'existe pas déjà . + * @returns {HTMLElement} - Le menu contextuel créé ou récupéré. */ function injectCustomContextMenu() { + // Récupère le menu contextuel existant par son ID let customContextMenu = document.getElementById(CUSTOM_CONTEXT_MENU); + + // Si le menu n'existe pas, on le crée if (!customContextMenu) { customContextMenu = document.createElement("div"); customContextMenu.id = CUSTOM_CONTEXT_MENU; @@ -43,6 +70,7 @@ function injectCustomContextMenu() { const addLexiconPath = browser.runtime.getURL("src/assets/icons/ajout_lexique.png"); const getDefinitionPath = browser.runtime.getURL("src/assets/icons/definition.png"); const loginPath = browser.runtime.getURL("src/assets/icons/connexion.png"); + // Construction du HTML du menu contextuel customContextMenu.innerHTML = ` <p id="selectedWord" style="margin: 0; padding: 0;">Mot sélectionné : Aucun</p> @@ -62,17 +90,23 @@ function injectCustomContextMenu() { </div> </div> `; + + // Ajoute le menu contextuel au corps du document document.body.appendChild(customContextMenu); + + // Configure les actions des boutons du menu contextuel setupCustomContextMenuActions(); } customContextMenu.addEventListener("mouseup", (e) => { e.stopPropagation(); }); + return customContextMenu; } /** * Renvoie le customContextMenu s'il existe, ou le crée. + * @returns {HTMLElement} - Le menu contextuel. */ function getOrCreateCustomContextMenu() { return document.getElementById(CUSTOM_CONTEXT_MENU) || injectCustomContextMenu(); @@ -95,7 +129,7 @@ function setupCustomContextMenuActions() { e.stopPropagation(); e.preventDefault(); const selectedText = getSelectedWord().trim(); - log("🔠Bouton Ajouter au lexique cliqué avec le mot :", selectedText); + log("Bouton Ajouter au lexique cliqué avec le mot :", selectedText); if (!selectedText) return; if (authToken) { browser.runtime.sendMessage({ action: "openLexiconBlock" }); @@ -148,16 +182,10 @@ function updateMenuVisibility() { } } -/** - * Récupère le texte affiché dans #selectedWord. - */ -function getSelectedWord() { - const selectedWordElement = document.getElementById("selectedWord"); - return selectedWordElement ? selectedWordElement.textContent.trim() : ""; -} - /** * Affiche le menu contextuel à la position du clic. + * @param {MouseEvent} event - L'événement de clic. + * @param {string} selectedText - Le texte sélectionné. */ async function showCustomContextMenu(event, selectedText) { const { extensionActive } = await browser.storage.local.get("extensionActive") || { extensionActive: false }; @@ -184,6 +212,9 @@ async function showCustomContextMenu(event, selectedText) { updateMenuVisibility(); } +/** + * Masque le menu contextuel personnalisé. + */ function hideCustomContextMenu() { const customContextMenu = document.getElementById(CUSTOM_CONTEXT_MENU); if (customContextMenu) { @@ -191,72 +222,45 @@ function hideCustomContextMenu() { } } -// Écoute globale pour la sélection de texte -document.addEventListener("mouseup", (event) => { - if (event.target.closest("#customContextMenu")) return; - const selectedText = window.getSelection().toString().trim(); - if (selectedText) { - log("Texte sélectionné :", selectedText); - getOrCreateCustomContextMenu(); - showCustomContextMenu(event, selectedText); - browser.runtime.sendMessage({ - action: "mot_selectionne", - selectedText, - }); - } else { - hideCustomContextMenu(); - } -}); - -// Écoute des messages entrants -browser.runtime.onMessage.addListener((message) => { - if (message.action === "refreshUI") { - log("🔄 Mise à jour du menu contextuel personnalisé."); - loadAuthToken().then(updateMenuVisibility); - } -}); - -// Initialisation au démarrage -loadAuthToken().then(() => { - getOrCreateCustomContextMenu(); - updateMenuVisibility(); -}); -browser.storage.onChanged.addListener((changes) => { - if (changes.accessToken) { - log("🔄 Token modifié dans le stockage, mise à jour du menu contextuel."); - loadAuthToken().then(updateMenuVisibility); - } -}); - // ───────────────────────────────────────────────────────────────────────────── -// Fonctions d'API pour l'ajout d'un mot via le sélecteur +// Fonctions pour l'ajout d'un mot via le sélecteur // ───────────────────────────────────────────────────────────────────────────── /** * Affiche le sélecteur pour choisir le lexique dans lequel ajouter le mot. + * @param {MouseEvent} event - L'événement de clic. + * @param {string} selectedText - Le texte sélectionné. */ async function showPicker(event, selectedText) { + // Récupère le sélecteur de lexique existant par son ID let picker = document.getElementById("lexiconPicker"); + + // Si le sélecteur n'existe pas, on le crée if (!picker) { picker = document.createElement("div"); picker.id = "lexiconPicker"; picker.addEventListener("mouseup", (e) => e.stopPropagation()); document.body.appendChild(picker); } - picker.innerHTML = ""; - const selectedLexicons = new Set(); + + picker.innerHTML = ""; + const selectedLexicons = new Set(); // Ensemble pour stocker les lexiques sélectionnés try { + // Récupère les lexiques disponibles en utilisant le token d'authentification const lexicons = await getLexicons(authToken); log("Lexicons récupérés :", lexicons); - const lexiconDescriptions = {}; + const lexiconDescriptions = {}; // Objet pour stocker les descriptions des lexiques + // Vérifie si des lexiques ont été récupérés if (!Array.isArray(lexicons) || lexicons.length === 0) { picker.innerHTML = "<p style='color:#333;'>Aucun lexique trouvé.</p>"; } else { + // Parcourt chaque lexique récupéré for (const lex of lexicons) { - const id = lex.id; - let name = ""; + const id = lex.id; // Récupère l'ID du lexique + let name = ""; // Initialise le nom du lexique + // Définit le nom en fonction de la catégorie du lexique if (lex.category === "User") { name = DEBUG ? `Lexique personnel : ${lex.user?.pseudo || "Inconnu"} (${lex.id})` @@ -267,29 +271,32 @@ async function showPicker(event, selectedText) { : `Lexique de groupe : ${lex.group?.name || "Inconnu"}`; } if (lex.language) { - name += ` [${lex.language}]`; + name += ` [${lex.language}]`; // Ajoute la langue si disponible } - lexiconDescriptions[id] = name; - const color = await getColorForLexicon(id); - const circleIcon = await createColorCircle(color, 28); + lexiconDescriptions[id] = name; // Stocke la description du lexique + const color = await getColorForLexicon(id); // Récupère la couleur du lexique + const circleIcon = await createColorCircle(color, 28); + // Crée un conteneur pour l'icône du lexique const iconContainer = document.createElement("div"); iconContainer.className = "lexicon-option"; iconContainer.dataset.lexiconId = id; iconContainer.title = name; + iconContainer.addEventListener("click", () => { if (selectedLexicons.has(id)) { - selectedLexicons.delete(id); - iconContainer.classList.remove("selected"); + selectedLexicons.delete(id); // Supprime l'ID si déjà sélectionné + iconContainer.classList.remove("selected"); // Retire la classe sélectionnée } else { - selectedLexicons.add(id); - iconContainer.classList.add("selected"); + selectedLexicons.add(id); // Ajoute l'ID si non sélectionné + iconContainer.classList.add("selected"); // Ajoute la classe sélectionnée } }); - iconContainer.appendChild(circleIcon); - picker.appendChild(iconContainer); + iconContainer.appendChild(circleIcon); // Ajoute l'icône au conteneur + picker.appendChild(iconContainer); // Ajoute le conteneur au sélecteur } + // Crée le bouton de confirmation pour ajouter le mot const confirmButton = document.createElement("button"); confirmButton.className = "confirmButton"; confirmButton.textContent = "Ajouter le mot"; @@ -301,32 +308,33 @@ async function showPicker(event, selectedText) { log(`🔠Vérification si le mot "${selectedText}" existe déjà dans les lexiques sélectionnés...`); let definitions = []; try { - definitions = await fetchLexiconDefinitions(selectedText); + definitions = await fetchLexiconDefinitions(selectedText); // Récupère les définitions du mot } catch (error) { log("Erreur lors de la récupération des définitions :", error); } - const existingLexiconIds = new Set(); + const existingLexiconIds = new Set(); // Ensemble pour stocker les IDs des lexiques existants if (Array.isArray(definitions)) { for (const def of definitions) { if (selectedLexicons.has(def.lexiconId)) { - existingLexiconIds.add(def.lexiconId); + existingLexiconIds.add(def.lexiconId); // Ajoute l'ID si le mot existe déjà } } } + // Alerte si le mot existe déjà dans les lexiques sélectionnés if (existingLexiconIds.size > 0) { 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)); + const lexiconsToAdd = [...selectedLexicons].filter(id => !existingLexiconIds.has(id)); // Filtre les lexiques à ajouter if (lexiconsToAdd.length === 0) { - return; + return; // Sort si aucun lexique à ajouter } try { log(`📡 Ajout du mot "${selectedText}" dans les lexiques :`, lexiconsToAdd); - const result = await AddWord(authToken, selectedText, lexiconsToAdd, false); + const result = await AddWord(authToken, selectedText, lexiconsToAdd, false); // Ajoute le mot aux lexiques log("Réponse API :", result); - await new Promise(resolve => setTimeout(resolve, 300)); - browser.runtime.sendMessage({ action: "refreshUI" }); - const successMsg = `✅ Mot ajouté avec succès dans : ${lexiconsToAdd.map(id => lexiconDescriptions[id]).join(", ")}`; + await new Promise(resolve => setTimeout(resolve, 300)); // Attente pour la réponse + browser.runtime.sendMessage({ action: "refreshUI" }); // Rafraîchit l'interface utilisateur + const successMsg = `✅ Mot ajouté avec succès dans : ${lexiconsToAdd.map(id => lexiconDescriptions[id]).join(", ")}`; // Message de succès picker.innerHTML = `<p style="color: green;">${successMsg}</p>`; setTimeout(() => picker.style.display = "none", 2000); browser.runtime.sendMessage({ @@ -334,7 +342,7 @@ async function showPicker(event, selectedText) { lexicons: successMsg }); } catch (error) { - log("⌠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); @@ -344,24 +352,28 @@ async function showPicker(event, selectedText) { }); } }); - picker.appendChild(confirmButton); + picker.appendChild(confirmButton); // Ajoute le bouton de confirmation au sélecteur } - const lexiconCount = Array.isArray(lexicons) ? lexicons.length : 0; - const baseWidthPerLexicon = 40; - const extraPadding = 20; - const calculatedWidth = lexiconCount * baseWidthPerLexicon + extraPadding; - picker.style.width = calculatedWidth + "px"; + // Calcule et définit la largeur et la position du sélecteur + const lexiconCount = Array.isArray(lexicons) ? lexicons.length : 0; + const baseWidthPerLexicon = 40; + const extraPadding = 20; + const calculatedWidth = lexiconCount * baseWidthPerLexicon + extraPadding; + picker.style.width = calculatedWidth + "px"; picker.style.left = event.pageX + "px"; picker.style.top = event.pageY + "px"; picker.style.display = "flex"; } catch (error) { - log("⌠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"; } } +/** + * Masque le sélecteur de lexique. + */ function hideLexiconPicker() { const picker = document.getElementById("lexiconPicker"); if (picker) { @@ -369,24 +381,76 @@ function hideLexiconPicker() { } } +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Écouteurs d'événements +// ───────────────────────────────────────────────────────────────────────────── +// Écoute globale pour la sélection de texte document.addEventListener("mouseup", (event) => { - const customContextMenu = document.getElementById(CUSTOM_CONTEXT_MENU); - const picker = document.getElementById("lexiconPicker"); + // Vérifie si le clic est à l'intérieur du menu contextuel, si oui, ne fait rien + if (event.target.closest("#customContextMenu")) return; + + // Récupère le texte sélectionné + const selectedText = window.getSelection().toString().trim(); + if (selectedText) { + log("Texte sélectionné :", selectedText); // Log le texte sélectionné + getOrCreateCustomContextMenu(); // Récupère ou crée le menu contextuel + showCustomContextMenu(event, selectedText); // Affiche le menu contextuel avec le texte sélectionné + // Envoie un message au runtime avec le texte sélectionné + browser.runtime.sendMessage({ + action: "mot_selectionne", + selectedText, + }); + } else { + hideCustomContextMenu(); + } +}); +// Écoute des messages entrants +browser.runtime.onMessage.addListener((message) => { + if (message.action === "refreshUI") { + log("Mise à jour du menu contextuel personnalisé."); + loadAuthToken().then(updateMenuVisibility); + } +}); + +// Initialisation au démarrage +loadAuthToken().then(() => { + getOrCreateCustomContextMenu(); // Récupère ou crée le menu contextuel + updateMenuVisibility(); // Met à jour la visibilité des boutons +}); + +// Écoute des changements dans le stockage +browser.storage.onChanged.addListener((changes) => { + // Vérifie si le token d'accès a changé + if (changes.accessToken) { + log("Token modifié dans le stockage, mise à jour du menu contextuel."); + loadAuthToken().then(updateMenuVisibility); // Recharge le token et met à jour la visibilité + } +}); + +// Écouteur pour masquer le menu contextuel et le sélecteur +document.addEventListener("mouseup", (event) => { + const customContextMenu = document.getElementById(CUSTOM_CONTEXT_MENU); // Récupère le menu contextuel + const picker = document.getElementById("lexiconPicker"); // Récupère le sélecteur de lexique + + // Masque le menu contextuel si le clic est en dehors de celui-ci if (customContextMenu && !customContextMenu.contains(event.target)) { hideCustomContextMenu(); } + // Masque le sélecteur si le clic est en dehors de celui-ci if (picker && !picker.contains(event.target)) { hideLexiconPicker(); } + // Récupère le texte sélectionné const selectedText = window.getSelection().toString().trim(); if (selectedText) { log("Texte sélectionné :", selectedText); showCustomContextMenu(event, selectedText); + // Envoie un message au runtime avec le texte sélectionné browser.runtime.sendMessage({ action: "mot_selectionne", selectedText, }); } -}); +}); \ No newline at end of file diff --git a/src/plugin/plugin.js b/src/plugin/plugin.js index 97ab4d10f98a92d03f3e6c076d94e0c9216e1f63..f95ae3890bded642a9276209a4c33485b8ae581b 100644 --- a/src/plugin/plugin.js +++ b/src/plugin/plugin.js @@ -1,25 +1,35 @@ -log("✅ plugin.js chargé avec succès !"); - -// ========================== -// Fonctions utilitaires -// ========================== +log("plugin.js chargé."); +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Fonctions utilitaires +// ───────────────────────────────────────────────────────────────────────────── +/** + * Récupère le token d'accès depuis le stockage local. + * @returns {Promise<string>} Le token d'accès. + */ async function getAccessToken() { const { accessToken } = await browser.storage.local.get("accessToken"); return accessToken; } -// ========================== -// Gestion de la connexion -// ========================== +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Gestion de la connexion +// ───────────────────────────────────────────────────────────────────────────── +/** + * Met à jour le bouton de connexion en fonction de l'état de connexion. + */ async function updateConnectionButton() { + // Récupération du token d'accès const accessToken = await getAccessToken(); + // Sélection du bouton de connexion const button = document.getElementById("auth-button"); + // Vérification si le bouton existe if (!button) { - log("⌠Le bouton de connexion n'a pas été trouvé."); + log("Le bouton de connexion n'a pas été trouvé."); return; } + // Mise à jour du bouton en fonction de l'état de connexion if (accessToken) { button.textContent = "Se déconnecter"; button.style.position = "relative"; @@ -38,56 +48,69 @@ async function updateConnectionButton() { button.appendChild(tooltip); } + // Gestion du clic sur le bouton button.onclick = async () => { - await browser.runtime.sendMessage({ action: "toggleAuth" }); - await updateConnectionButton(); + await browser.runtime.sendMessage({ action: "toggleAuth" }); // Envoi d'un message pour changer l'état d'authentification + await updateConnectionButton(); // Mise à jour du bouton après le changement d'état }; } -// ========================== -// Gestion de la sélection des langues -// ========================== +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Gestion de la sélection des langues +// ───────────────────────────────────────────────────────────────────────────── +/** + * Met à jour 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(); + // Vérification si l'utilisateur est connecté if (!accessToken) { languageSelection.innerHTML = "<p style='color: red;'>Veuillez vous connecter.</p>"; return; } + // Récupération des lexiques et langues de l'utilisateur const lexicons = await getLexicons(accessToken); const userLanguages = [...new Set(lexicons.map(lex => lex.language))]; + // Récupération des langues suivies depuis le stockage local 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; + languageSelection.innerHTML = "<p style='color: red;'>Aucun lexique personnel trouvé.</p>"; // Message d'erreur si aucun lexique + return; } + // Création des boutons pour chaque langue userLanguages.forEach(lang => { const langButton = document.createElement("div"); langButton.classList.add("lang-option"); langButton.textContent = lang.toUpperCase(); langButton.dataset.value = lang; + // Vérification si la langue est suivie if (trackedLanguages && trackedLanguages.includes(lang)) { langButton.classList.add("selected"); } + // Gestion du clic sur le bouton de langue langButton.addEventListener("click", () => { langButton.classList.toggle("selected"); }); languageSelection.appendChild(langButton); }); - log("✅ Sélection des langues mise à jour avec :", userLanguages); + log("Sélection des langues mise à jour avec :", userLanguages); } -// ========================== -// Gestion principale de l'extension -// ========================== +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Gestion principale de l'extension +// ───────────────────────────────────────────────────────────────────────────── +/** + * Met à jour l'extension. + */ async function updateExtension() { // Récupération de tous les états nécessaires const accessToken = await getAccessToken(); @@ -111,21 +134,25 @@ async function updateExtension() { pyodideSimplemmaReady: false }; + // Vérification si l'utilisateur est connecté const isLoggedIn = !!accessToken; // Mise à jour du bouton d'activation de l'extension const toggleExtensionBtn = document.getElementById("toggleExtensionBtn"); if (toggleExtensionBtn) { + // Mise à jour du texte du bouton en fonction de l'état de l'extension 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"; + // Suppression de l'ancien tooltip s'il existe const existingTooltipExt = toggleExtensionBtn.querySelector('.tooltip'); if (existingTooltipExt) { existingTooltipExt.remove(); } + // Création d'un nouveau tooltip const tooltipExt = document.createElement("span"); tooltipExt.className = "tooltip"; tooltipExt.style.opacity = "1 !important"; @@ -148,6 +175,7 @@ async function updateExtension() { const toggleStatsBtn = document.getElementById("toggleStatsBtn"); const openStats = document.getElementById("open-stats"); + // Affichage ou masquage des options de statistiques if (statsOptions) { statsOptions.style.display = (isLoggedIn && extensionActive) ? "block" : "none"; } @@ -161,9 +189,11 @@ async function updateExtension() { toggleStatsBtn.style.position = "relative"; toggleStatsBtn.className = "tooltip-container"; + // Suppression de l'ancien tooltip s'il existe const existingTooltipStats = toggleStatsBtn.querySelector('.tooltip'); if (existingTooltipStats) { existingTooltipStats.remove(); } + // Création d'un nouveau tooltip pour les statistiques const tooltipStats = document.createElement("span"); tooltipStats.className = "tooltip"; tooltipStats.style.opacity = "1 !important"; @@ -179,7 +209,7 @@ async function updateExtension() { toggleStatsBtn.appendChild(tooltipStats); } - + // Affichage ou masquage du bouton pour ouvrir les statistiques if (openStats) { openStats.style.display = (isLoggedIn && extensionActive && isTrackingActive) ? "block" : "none"; } @@ -190,18 +220,22 @@ async function updateExtension() { const autoAddOptions = document.getElementById("auto-add-options"); const saveOptionsBtn = document.getElementById("save-options"); + // Affichage ou masquage du conteneur d'ajout automatique if (autoAddContainer) { autoAddContainer.style.display = (isLoggedIn && extensionActive) ? "block" : "none"; } + // Mise à jour de l'état de la case à cocher d'ajout automatique if (autoAddCheckbox && isLoggedIn) { autoAddCheckbox.checked = autoAdd; } + // Affichage ou masquage des options d'ajout automatique if (autoAddOptions) { autoAddOptions.classList.toggle("hidden", !autoAdd); } + // Affichage ou masquage du bouton de sauvegarde des options if (saveOptionsBtn) { saveOptionsBtn.classList.toggle("hidden", !autoAdd); } @@ -235,6 +269,9 @@ async function updateExtension() { }); } +/** + * Gère le clic sur le bouton d'activation de l'extension (analyse). + */ async function handleToggleExtension() { const accessToken = await getAccessToken(); if (!accessToken) return; @@ -242,54 +279,77 @@ async function handleToggleExtension() { await updateConnectionButton(); } +/** + * Effectue le changement d'état de l'extension (analyse). + */ async function proceedToggleExtension() { + // Récupération de l'état actuel de l'extension et du suivi const { extensionActive, isTrackingActive } = await browser.storage.local.get({ - extensionActive: false, - isTrackingActive: false + extensionActive: false, // Valeur par défaut si non trouvée + isTrackingActive: false // Valeur par défaut si non trouvée }); log("État actuel de extensionActive avant changement :", extensionActive); const newState = !extensionActive; + // Mise à jour de l'état de l'extension dans le stockage local await browser.storage.local.set({ extensionActive: newState }); log("Nouvel état de extensionActive :", newState); + // Envoi d'un message pour mettre à jour l'interface utilisateur browser.runtime.sendMessage({ action: "updateUI", extensionActive: newState }); + // Si l'extension est désactivée if (!newState) { + // Désactivation du suivi await browser.storage.local.set({ isTrackingActive: false }); + // Ouverture de la page des statistiques si le suivi était actif if (isTrackingActive) window.open("stats.html", "_blank"); + // Envoi d'un message pour fermer les blocs de la barre latérale browser.runtime.sendMessage({ action: "closeSidebarBlocks" }); } + // Envoi d'un message pour changer l'état de l'extension browser.runtime.sendMessage({ action: "toggleExtension", isActive: newState }); + // Mise à jour de l'extension après le changement d'état await updateExtension(); } -// ========================== -// Écouteurs d'événements -// ========================== +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Écouteurs d'événements +// ───────────────────────────────────────────────────────────────────────────── +/** + * Met à jour le bouton de connexion et l'extension lorsque le DOM est chargé. + */ document.addEventListener("DOMContentLoaded", async () => { await updateConnectionButton(); await updateExtension(); }); -// Gestion des statistiques +/** + * Gère le clic sur le bouton des statistiques. + */ document.getElementById("toggleStatsBtn")?.addEventListener("click", async () => { const accessToken = await getAccessToken(); if (!accessToken) return; + // Récupération de l'état actuel du suivi const current = await browser.storage.local.get("isTrackingActive"); const newState = !current.isTrackingActive; await browser.storage.local.set({ isTrackingActive: newState }); + // Si le suivi est désactivé, désactiver également l'ajout automatique if (!newState) { await browser.storage.local.set({ autoAdd: false }); } + // Envoi d'un message pour changer l'état des statistiques browser.runtime.sendMessage({ command: "toggle-stats", isActive: newState }); + // Si le suivi est activé, envoyer un message pour pyodide-simplemma if (newState) { browser.runtime.sendMessage({ command: "pyodide-simplemma" }); } + + // Mise à jour de l'affichage des lexiques if (isUpdatingLexicons) return; isUpdatingLexicons = true; await updateLexiconsDisplay(); @@ -297,31 +357,39 @@ document.getElementById("toggleStatsBtn")?.addEventListener("click", async () => await updateExtension(); }); -// Gestion de l'ajout automatique +/** + * Gère le changement de l'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); + + // Si l'ajout automatique est désactivé, mettre à jour l'état if (!isChecked) { await browser.storage.local.set({ autoAdd: false }); await updateExtension(); } }); -// Sauvegarde des options +/** + * Sauvegarde des options. + */ document.getElementById("save-options")?.addEventListener("click", async () => { const autoAdd = document.getElementById("auto-add").checked; const threshold = parseInt(document.getElementById("threshold").value, 10); const selectedLanguages = Array.from(document.querySelectorAll("#language-selection .lang-option.selected")) - .map(option => option.dataset.value); + .map(option => option.dataset.value); const errorMessage = document.getElementById("error-message"); + // Vérification si l'ajout automatique est activé sans langues sélectionnées if (autoAdd && selectedLanguages.length === 0) { - errorMessage?.classList.remove("hidden"); - return; + errorMessage?.classList.remove("hidden"); // Affichage du message d'erreur + return; } errorMessage?.classList.add("hidden"); + // Sauvegarde des options dans le stockage local await browser.storage.local.set({ autoAdd, threshold, @@ -332,16 +400,35 @@ document.getElementById("save-options")?.addEventListener("click", async () => { log("Options sauvegardées et interface mise à jour"); }); -// Ouverture des statistiques +/** + * Ouvre la page des statistiques. + */ document.getElementById("open-stats")?.addEventListener("click", () => { window.open("stats.html", "_blank"); }); -// ========================== -// Gestion des messages et du stockage -// ========================== +/** + * Affiche le message d'information sur les langues suivies. + */ +document.addEventListener("DOMContentLoaded", () => { + const optionRows = document.querySelectorAll('.option-row label'); + optionRows.forEach(label => { + if (label.textContent.trim() === "Langues suivies") { + label.classList.add("tooltip-container"); + + const tooltip = document.createElement("span"); + tooltip.className = "tooltip tooltip-langues-suivies"; + tooltip.textContent = "Choisissez une ou plusieurs langues parmi celles de vos lexiques personnels"; + label.appendChild(tooltip); + } + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Gestion des messages +// ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message) => { - log("📩 Message reçu dans plugin.js :", message); + log("Message reçu dans plugin.js :", message); if (message.action === "updateUI") { await updateExtension(); } else if (message.action === "notify") { @@ -355,9 +442,9 @@ browser.storage.onChanged.addListener((changes, area) => { } }); -// ========================== -// Gestion des notifications -// ========================== +/** + * Affiche une notification. + */ function showNotification(message) { const notificationBox = document.getElementById("extension-notification"); const notificationText = document.getElementById("notification-text"); @@ -370,10 +457,13 @@ function showNotification(message) { notificationBox.classList.add("hidden"); }, { once: true }); } else { - log("⌠Impossible d'afficher la notification : élément manquant."); + log("Impossible d'afficher la notification : élément manquant."); } } +/** + * Gère les messages reçus du worker. + */ function handleWorkerMessage(event) { const data = event.data; log("[Background] Message du WebWorker :", data); @@ -387,17 +477,3 @@ function handleWorkerMessage(event) { } } - -document.addEventListener("DOMContentLoaded", () => { - const optionRows = document.querySelectorAll('.option-row label'); - optionRows.forEach(label => { - if (label.textContent.trim() === "Langues suivies") { - label.classList.add("tooltip-container"); - - const tooltip = document.createElement("span"); - tooltip.className = "tooltip tooltip-langues-suivies"; - tooltip.textContent = "Choisissez une ou plusieurs langues parmi celles de vos lexiques personnels"; - label.appendChild(tooltip); - } - }); -}); diff --git a/src/sidebar/sidebar.js b/src/sidebar/sidebar.js index fd756701445b7068fdbd658510171cec9aab04c6..171a924156277d01d918b2d0f0d3c248173331fa 100644 --- a/src/sidebar/sidebar.js +++ b/src/sidebar/sidebar.js @@ -1,20 +1,18 @@ -log("✅ sidebar.js chargé !"); -log( - "🌠Vérification API browser :", - typeof browser !== "undefined" ? "✅ Disponible" : "⌠Non disponible" -); - - // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Variables globales +// â–Œ Variables globales et logs // ───────────────────────────────────────────────────────────────────────────── -let authToken = window.authToken; -window.authToken = authToken; -let isUpdatingLexicons = false; +log("sidebar.js chargé."); +let authToken = window.authToken; // Token d'authentification de l'utilisateur +window.authToken = authToken; // Stockage du token dans l'objet window +let isUpdatingLexicons = false; // Indicateur pour savoir si les lexiques sont en cours de mise à jour // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Fonctions liées au token +// â–Œ Fonctions liées à la connexion et à l'activation de l'analyse // ───────────────────────────────────────────────────────────────────────────── +/** + * Récupère le token d'authentification depuis le stockage local. + * @returns {Promise<string|null>} Le token d'authentification ou null si non trouvé. + */ async function getAuthTokenFromStorage() { try { const { accessToken } = await browser.storage.local.get("accessToken"); @@ -26,24 +24,55 @@ async function getAuthTokenFromStorage() { } log("âš ï¸ Aucun token trouvé dans le stockage local."); } catch (error) { - log("⌠Erreur lors de la récupération du token :", error); + log("Erreur lors de la récupération du token :", error); } return null; } +/** + * Gère le clic sur le bouton d'authentification. + * Envoie un message pour basculer l'état d'authentification et actualise l'interface. + */ +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."; + } +} + +/** + * Vérifie l'état d'activation de l'extension. + * @returns {Promise<boolean>} L'état d'activation de l'extension. + */ +async function checkAnalysisStatus() { + const { extensionActive } = await browser.storage.local.get("extensionActive"); + return extensionActive; +} + // ───────────────────────────────────────────────────────────────────────────── // â–Œ Mise à jour de l'interface utilisateur (UI) // ───────────────────────────────────────────────────────────────────────────── +/** + * Met à jour le texte du bouton d'authentification en fonction de l'état de connexion. + * @param {boolean} isLoggedIn - Indique si l'utilisateur est connecté. + */ function updateAuthButton(isLoggedIn) { const authButton = document.getElementById("auth-button"); if (authButton) { authButton.textContent = isLoggedIn ? "Se déconnecter" : "Se connecter"; - log("🔄 Bouton d'authentification mis à jour :", authButton.textContent); + log("Bouton d'authentification mis à jour :", authButton.textContent); } else { console.warn("âš ï¸ Bouton d'authentification (#auth-button) introuvable."); } } +/** + * Bascule la visibilité des éléments en fonction de l'état de connexion. + * @param {boolean} isLoggedIn - Indique si l'utilisateur est connecté. + */ function toggleElementsVisibility(isLoggedIn) { const elementsToShowOrHide = [ { id: "add-to-lexiques", shouldShow: isLoggedIn }, @@ -62,6 +91,10 @@ function toggleElementsVisibility(isLoggedIn) { }); } +/** + * Affichage ou non le message de surlignage en fonction de l'état de connexion. + * @param {boolean} isLoggedIn - Indique si l'utilisateur est connecté. + */ function toggleHighlightMessage(isLoggedIn) { const highlightNote = document.getElementById("highlight-note"); if (highlightNote) { @@ -73,27 +106,33 @@ function toggleHighlightMessage(isLoggedIn) { * Met à jour l'état global de la barre latérale (bouton d'authentification, etc.) */ async function refreshSidebarState() { - log("🔄 Début de l'actualisation de la barre latérale..."); + log("Début de l'actualisation de la barre latérale..."); + + // Récupère le token d'authentification et détermine si l'utilisateur est connecté const token = await getAuthTokenFromStorage(); const isLoggedIn = !!token; + // Met à jour l'état du bouton d'authentification et la visibilité des éléments updateAuthButton(isLoggedIn); toggleElementsVisibility(isLoggedIn); toggleHighlightMessage(isLoggedIn); + // Vérifie si l'analyse est activée const isAnalysisEnabled = await checkAnalysisStatus(); if (isLoggedIn) { + // Si l'utilisateur est connecté, gère l'affichage des blocs et récupère les lexiques hideBlocks(!isAnalysisEnabled); + // Met à jour les couleurs des lexiques si ce n'est pas déjà fait if (!window.lexiconColorsUpdated) { await updateLexiconColors(authToken); window.lexiconColorsUpdated = true; } await fetchLexicons(); } else { - // Si l'utilisateur n'est pas connecté, on ferme tous les blocs - hideBlocks(true); // Masquer tous les blocs + // Si l'utilisateur n'est pas connecté, masque les blocs et affiche un message + hideBlocks(true); const lexiquesContainer = document.getElementById("lexiques"); if (lexiquesContainer) { @@ -107,7 +146,7 @@ async function refreshSidebarState() { } } - // Afficher un message d'activation de l'analyse selon le statut de connexion + // Affiche un message d'activation de l'analyse selon le statut de connexion const messageContainer = document.getElementById("messageContainer"); if (!isLoggedIn) { if (messageContainer) { @@ -125,9 +164,13 @@ async function refreshSidebarState() { } } - log("✅ Barre latérale actualisée. Utilisateur connecté :", isLoggedIn); + log("Barre latérale actualisée. Utilisateur connecté :", isLoggedIn); } + +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Gestion des blocs (Affichage) +// ───────────────────────────────────────────────────────────────────────────── /** * Bascule l'affichage d'un bloc. * @param {string} blockId - L'ID du conteneur à basculer. @@ -170,6 +213,11 @@ function openBlock(blockId, btn) { } } +/** + * Ferme un bloc s'il est ouvert et met à jour le bouton de bascule. + * @param {string} blockId - L'ID du conteneur à fermer. + * @param {HTMLElement} [btn] - (Optionnel) Le bouton de bascule à mettre à jour. + */ function closeBlock(blockId, btn) { const block = document.getElementById(blockId); if (block && !block.classList.contains("hidden")) { @@ -189,43 +237,54 @@ function closeBlock(blockId, btn) { } } +/** + * Masque ou affiche les blocs en fonction de l'état spécifié. + * @param {boolean} shouldHide - Indique si les blocs doivent être masqués. + */ 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 + block.classList.add("hidden"); } else { - block.classList.remove("hidden"); // Afficher le bloc + block.classList.remove("hidden"); } } }); } // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Gestion des lexiques (Affichage) +// â–Œ Gestion de l'affichage des lexiques et du surlignage // ───────────────────────────────────────────────────────────────────────────── - +/** + * Récupère les lexiques depuis l'API. + */ async function fetchLexicons() { try { - log("🔄 Début de la récupération des lexiques..."); - log("🔑 Token utilisé :", authToken); + log("Début de la récupération des lexiques..."); + log("Token utilisé :", authToken); + // Vérifie si le token d'authentification est disponible if (!authToken) { throw new Error("âš ï¸ Aucun token disponible. Veuillez vous connecter."); } + // Récupère les lexiques depuis l'API const lexicons = await getLexicons(authToken); - log("📚 Réponse brute de l'API :", lexicons); + log("Réponse brute de l'API :", lexicons); + // Vérifie si la réponse est un tableau et s'il contient des lexiques if (!Array.isArray(lexicons) || lexicons.length === 0) { throw new Error("âš ï¸ Aucun lexique trouvé."); } + // Vide la carte des lexiques existants lexiconMap.clear(); lexicons.forEach((lex) => { let lexiconName = ""; + // Crée le nom du lexique en fonction de sa catégorie if (lex.category === "User") { lexiconName = DEBUG ? `Lexique personnel : ${lex.user?.pseudo || "Inconnu"} (${lex.id})` @@ -240,9 +299,11 @@ async function fetchLexicons() { } else { lexiconName = DEBUG ? `Lexique : ${lex.id}` : "Lexique" ; } + // Ajoute le lexique à la carte avec son ID comme clé lexiconMap.set(lex.id, lexiconName); }); + // Affiche les lexiques avec des checkboxes await displayLexiconsWithCheckbox(lexicons.map((lex) => ({ lexiconName: lex.category === "User" @@ -256,10 +317,12 @@ async function fetchLexicons() { active: lex.active || false, }))); - // Restaurer l'état des boutons après l'affichage + + // Restaure l'état des boutons de surlignage await restoreHighlightingState(); } catch (error) { - log("⌠Erreur lors du chargement des lexiques :", error.message); + // Gère les erreurs lors du chargement des lexiques + 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."; @@ -273,32 +336,43 @@ async function fetchLexicons() { */ async function displayLexiconsWithCheckbox(lexicons) { const lexiquesContainer = document.getElementById("lexiques"); + // Vérifie si le conteneur des lexiques existe if (!lexiquesContainer) { console.warn("âš ï¸ Ã‰lément #lexiques introuvable."); return; } + + // Vide le conteneur avant d'afficher les nouveaux lexiques lexiquesContainer.innerHTML = ""; + + // Vérifie si la liste des lexiques est vide if (lexicons.length === 0) { log("âš ï¸ Aucun lexique à afficher."); lexiquesContainer.textContent = "Aucun lexique disponible."; return; } + // Parcourt chaque lexique pour créer et afficher les éléments correspondants for (const { lexiconName, lexiconId, active } of lexicons) { + // Vérifie si le lexique est déjà affiché if (lexiquesContainer.querySelector(`div[data-lexicon-id="${lexiconId}"]`)) { - continue; // Si oui, passez à l'itération suivante + continue; } + // Crée un conteneur pour le lexique const lexiqueDiv = document.createElement("div"); lexiqueDiv.className = "lexique-item"; + // Récupère la couleur associée au lexique et crée un cercle de couleur const color = await getColorForLexicon(lexiconId); const circleIcon = createColorCircle(color, 24); + // Crée un conteneur pour l'icône de couleur const iconDiv = document.createElement("div"); iconDiv.className = "lexique-icon"; iconDiv.appendChild(circleIcon); + // Crée un élément pour le nom du lexique const labelSpan = document.createElement("span"); labelSpan.className = "lexique-label"; labelSpan.textContent = lexiconName; @@ -332,7 +406,7 @@ async function displayLexiconsWithCheckbox(lexicons) { highlightTooltip.className = "tooltip"; highlightTooltip.textContent = "Activer/Désactiver le surlignage des mots du lexique"; - // Mise à jour du gestionnaire d'événements + // Mise à jour du gestionnaire d'événements pour le bouton de surlignage highlightButton.addEventListener("click", async () => { let currentState = highlightButton.dataset.active === "true"; let newState = !currentState; @@ -341,7 +415,7 @@ async function displayLexiconsWithCheckbox(lexicons) { highlightButton.dataset.active = newState ? "true" : "false"; highlightButton.classList.toggle("active", newState); - // Sauvegarder l'état dans le storage local + // Sauvegarde l'état dans le stockage 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 }); @@ -350,6 +424,7 @@ async function displayLexiconsWithCheckbox(lexicons) { } }); + // Ajoute les éléments au conteneur du lexique highlightButton.appendChild(feutreIcon); highlightButton.appendChild(highlightTooltip); lexiqueDiv.appendChild(iconDiv); @@ -359,6 +434,7 @@ async function displayLexiconsWithCheckbox(lexicons) { lexiquesContainer.appendChild(lexiqueDiv); } + // Ajuste la position des tooltips après un court délai setTimeout(() => { const menu = document.getElementById("menu"); if (!menu) return; @@ -372,6 +448,7 @@ async function displayLexiconsWithCheckbox(lexicons) { tooltip.style.transform = 'translateX(-50%) translateY(-5px)'; const tooltipRect = tooltip.getBoundingClientRect(); + // Ajuste la position du tooltip si elle déborde du menu if (tooltipRect.left < menuRect.left) { const overflowLeft = menuRect.left - tooltipRect.left; tooltip.style.transform = `translateX(calc(-100% + ${overflowLeft}px)) translateY(-5px)`; @@ -383,18 +460,23 @@ async function displayLexiconsWithCheckbox(lexicons) { }, 100); } +/** + * Met à jour l'affichage des lexiques. + */ async function updateLexiconsDisplay() { - const token = await getAuthTokenFromStorage(); // Récupérer le token d'authentification + const token = await getAuthTokenFromStorage(); 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 + const lexicons = await getLexicons(token); + await displayLexiconsWithCheckbox(lexicons); } -// Ajouter une fonction pour restaurer l'état des boutons au chargement +/** + * Restaure l'état des boutons de surlignage au chargement. + */ async function restoreHighlightingState() { try { const { activeLexicons } = await browser.storage.local.get("activeLexicons"); @@ -412,6 +494,9 @@ async function restoreHighlightingState() { } } +/** + * Initialise la boîte modale pour l'affichage des définitions. + */ function initModal() { log("initModal appelé"); const modalOverlay = document.getElementById("modalOverlay"); @@ -433,27 +518,44 @@ function initModal() { if (event.target === modalOverlay) closeDefinitionPopup(); }); } - document.addEventListener("DOMContentLoaded", initModal); -// ───────────────────────────────────────────────────────────────────────────── -// â–Œ Bouton de connexion/déconnexion -// ───────────────────────────────────────────────────────────────────────────── -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."; +/** + * Bascule l'état de surlignage d'un lexique. + * @param {number} lexiconId - L'ID du lexique à basculer. + * @param {boolean} isActive - Indique si le surlignage doit être activé ou désactivé. + */ +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); } } // ───────────────────────────────────────────────────────────────────────────── // â–Œ Ajout d'un mot au(x) lexique(s) // ───────────────────────────────────────────────────────────────────────────── +/** + * Gère le clic sur le bouton d'ajout de mot. + * Vérifie le token et le mot sélectionné, puis ajoute le mot aux lexiques sélectionnés. + */ async function handleAddWordClick() { openBlock("menuContent"); + // 1) Vérifier la présence du token et du mot if (!authToken) { console.warn("âš ï¸ Pas de token d'authentification : impossible d'ajouter le mot."); @@ -463,10 +565,13 @@ async function handleAddWordClick() { const selectedWordElement = document.getElementById("motSelectionne"); const lexiconResultElement = document.getElementById("lexiconResult"); + // Vérifie si l'élément contenant le mot sélectionné existe if (!selectedWordElement) { console.warn("âš ï¸ Ã‰lément #motSelectionne introuvable."); return; } + + // Récupère le mot sélectionné et le "nettoie" const selectedWord = selectedWordElement.textContent.trim(); if (!selectedWord || selectedWord === "Aucun mot sélectionné") { console.warn("âš ï¸ Aucun mot à ajouter."); @@ -478,6 +583,7 @@ async function handleAddWordClick() { const checkboxList = document.querySelectorAll("#lexiques .lexique-checkbox:checked"); const selectedLexiconIds = Array.from(checkboxList).map(cb => parseInt(cb.dataset.lexiconId, 10)); + // Vérifie si au moins un lexique a été sélectionné if (selectedLexiconIds.length === 0) { console.warn("âš ï¸ Aucun lexique sélectionné."); if (lexiconResultElement) lexiconResultElement.textContent = "Veuillez cocher au moins un lexique."; @@ -493,6 +599,7 @@ async function handleAddWordClick() { } const existingLexiconIds = new Set(); + // Vérifie si le mot existe déjà dans les lexiques sélectionnés if (Array.isArray(definitions)) { for (const def of definitions) { if (selectedLexiconIds.includes(def.lexiconId)) { @@ -501,7 +608,7 @@ async function handleAddWordClick() { } } - // 4) Déterminer les lexiques où ajouter le mot (ceux qui n'ont pas déjà le mot) + // 4) Déterminer les lexiques où ajouter le mot const lexiconsToAdd = selectedLexiconIds.filter(id => !existingLexiconIds.has(id)); // Si le mot existe déjà dans certains lexiques, on affiche le message @@ -516,6 +623,7 @@ async function handleAddWordClick() { } } + // Si aucun lexique n'est disponible pour ajouter le mot, on sort de la fonction if (lexiconsToAdd.length === 0) { return; } @@ -551,21 +659,22 @@ async function handleAddWordClick() { } } - // ───────────────────────────────────────────────────────────────────────────── // â–Œ Réception des messages // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message) => { - log("📩 Message reçu dans sidebar.js :", message); + log("Message reçu dans sidebar.js :", message); + // Vérifie si le message contient une action if (message.action) switch (message.action) { case "refreshUI": - log("🔄 Demande de rafraîchissement de la barre latérale."); + log("Demande de rafraîchissement de la barre latérale."); await refreshSidebarState(); break; case "mot_selectionne": + // Gère le mot sélectionné if (message.selectedText) { log("ðŸ–‹ï¸ Mot sélectionné :", message.selectedText); const selectedWordElement = document.getElementById("motSelectionne"); @@ -583,20 +692,23 @@ browser.runtime.onMessage.addListener(async (message) => { break; case "getDefinition": + // Recherche des définitions pour le mot sélectionné if (message.selectedText) { - log("📖 Recherche des définitions pour :", message.selectedText); + log("Recherche des définitions pour :", message.selectedText); openBlock("definitionContent"); await window.showDefinitions(message.selectedText); } break; case "showDefinitions": + // Affiche les définitions reçues if (Array.isArray(message.definitions)) { window.displayDefinitions(message.definitions); } break; case "fetchWiktionaryDefinitionResponse": + // Gère la réponse de définition du Wiktionnaire if (message.selectedText) { log(`📖 Réception de la définition du Wiktionnaire pour '${message.selectedText}'`); window.displayDefinitions(message.definitions); @@ -604,11 +716,13 @@ browser.runtime.onMessage.addListener(async (message) => { break; case "showLexiconResult": - log("📚 Résultat des lexiques reçus :", message.lexicons); + // Affiche le résultat des lexiques reçus + log("Résultat des lexiques reçus :", message.lexicons); window.displayLexiconResults(message.lexicons); break; case "addWordResult": + // Met à jour le résultat de l'ajout de mot const lexiconResultElement = document.getElementById("lexiconResult"); if (lexiconResultElement) { lexiconResultElement.innerHTML = message.lexicons; @@ -616,10 +730,12 @@ browser.runtime.onMessage.addListener(async (message) => { break; case "addToLexicon": + // Gère l'ajout d'un mot au lexique handleAddWordClick(); break; case "openLexiconBlock": + // Ouvre le bloc des lexiques openBlock("menuContent"); break; @@ -630,8 +746,8 @@ browser.runtime.onMessage.addListener(async (message) => { break; case "updateUI": + // Met à jour l'interface utilisateur en fonction de l'état de l'extension if (!message.extensionActive) { - // Fermer tous les blocs hideBlocks(true); } else { hideBlocks(false); @@ -643,17 +759,21 @@ browser.runtime.onMessage.addListener(async (message) => { return; case "saveToken": + // Sauvegarde le token d'authentification authToken = message.token; break; case "closeSidebarBlocks": + // Masque les blocs de la barre latérale hideBlocks(true); break; } + // Vérifie si le message contient une commande if (message.command) { switch (message.command) { case "activate-highlighting": + // Active le surlignage pour le lexique spécifié const highlightButton = document.querySelector(`button[data-lexicon-id="${message.lexiconId}"]`); if (highlightButton) { highlightButton.dataset.active = "true"; @@ -662,6 +782,7 @@ browser.runtime.onMessage.addListener(async (message) => { break; case "deactivate-highlighting": + // Désactive le surlignage pour le lexique spécifié const highlightButtonOff = document.querySelector(`button[data-lexicon-id="${message.lexiconId}"]`); if (highlightButtonOff) { highlightButtonOff.dataset.active = "false"; @@ -673,7 +794,8 @@ browser.runtime.onMessage.addListener(async (message) => { break; case "register-highlighting-script": - log("✅ Script de surlignage enregistré"); + // Enregistre le script de surlignage et met à jour le token d'authentification + log("Script de surlignage enregistré"); browser.runtime.sendMessage({ command: "updateAuthToken", token: authToken @@ -681,11 +803,13 @@ browser.runtime.onMessage.addListener(async (message) => { break; case "updateAuthToken": + // Met à jour le token d'authentification authToken = message.token; window.authToken = message.token; break; default: + // Gère les actions inconnues console.warn("âš ï¸ Action inconnue reçue :", message.command); } } @@ -695,16 +819,22 @@ browser.runtime.onMessage.addListener(async (message) => { // â–Œ DOMContentLoaded // ───────────────────────────────────────────────────────────────────────────── document.addEventListener("DOMContentLoaded", async () => { - log("📦 DOM entièrement chargé. Initialisation de la sidebar."); + log("DOM entièrement chargé. Initialisation de la sidebar."); + + // Récupère le token d'authentification et l'assigne à l'objet window authToken = await getAuthTokenFromStorage(); window.authToken = authToken; + + // Actualise l'état de la barre latérale await refreshSidebarState(); + // Gère le clic sur le bouton d'authentification const authButton = document.getElementById("auth-button"); if (authButton) { authButton.addEventListener("click", handleAuthButtonClick); } + // Gère le clic sur le bouton de recherche de définition const chercherDefButton = document.querySelector("#chercherDef"); if (chercherDefButton) { chercherDefButton.addEventListener("click", async () => { @@ -717,13 +847,13 @@ document.addEventListener("DOMContentLoaded", async () => { console.warn("âš ï¸ Aucun mot sélectionné pour la recherche."); } }); - log("✅ Bouton #chercherDef détecté dans le DOM."); + log("Bouton #chercherDef détecté dans le DOM."); chercherDefButton.style.pointerEvents = "auto"; chercherDefButton.style.display = "block"; chercherDefButton.style.visibility = "visible"; chercherDefButton.disabled = false; } else { - log("⌠ERREUR : Bouton #chercherDef introuvable."); + log("ERREUR : Bouton #chercherDef introuvable."); } // Écouteur pour la case à cocher "toggle-definitions" @@ -742,7 +872,7 @@ document.addEventListener("DOMContentLoaded", async () => { }); } - // Bouton "Ajouter le mot sélectionné" + // Gère le clic sur le bouton "Ajouter le mot sélectionné" const addWordButton = document.getElementById("add-word-button"); if (addWordButton) { addWordButton.addEventListener("click", handleAddWordClick); @@ -750,7 +880,7 @@ document.addEventListener("DOMContentLoaded", async () => { console.warn("âš ï¸ Bouton #add-word-button introuvable dans le DOM."); } - // Écouteur pour la case à cocher "toggle-definitions" + // Gère les boutons de bascule pour afficher/masquer les blocs const toggleButtons = document.querySelectorAll(".toggle-btn"); toggleButtons.forEach((btn) => { if (!btn.textContent.trim()) { @@ -767,10 +897,12 @@ document.addEventListener("DOMContentLoaded", async () => { } }); + // Masque tous les blocs au chargement document.querySelectorAll('.block-content').forEach(block => { block.classList.add('hidden'); }); + // Réinitialise les boutons de bascule document.querySelectorAll('.toggle-btn').forEach(btn => { btn.textContent = '+'; btn.style.fontSize = '15px'; @@ -788,41 +920,18 @@ document.addEventListener("DOMContentLoaded", async () => { }); }); - document.querySelectorAll('.toggle-btn').forEach(btn => { - btn.addEventListener('click', function() { - const blockContent = this.parentElement.nextElementSibling; - if (blockContent.classList.contains('hidden')) { - blockContent.classList.remove('hidden'); - this.textContent = '–'; - } else { - blockContent.classList.add('hidden'); - this.textContent = '+'; - } - }); - }); - -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); +// Gère les clics sur les boutons de bascule +document.querySelectorAll('.toggle-btn').forEach(btn => { + btn.addEventListener('click', function() { + const blockContent = this.parentElement.nextElementSibling; + if (blockContent.classList.contains('hidden')) { + blockContent.classList.remove('hidden'); + this.textContent = '–'; + } else { + blockContent.classList.add('hidden'); + this.textContent = '+'; } + }); +}); - 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 58695258e268059be0e86ce3f9c4232309c96903..efcd5749ab1841bcde21902e6142c221a7067bfa 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,4 +1,5 @@ -log("✅ api.js chargé correctement"); +log("api.js chargé."); + window.authToken = null; // ───────────────────────────────────────────────────────────────────────────── @@ -27,21 +28,27 @@ document.addEventListener("mouseup", () => { * @throws {Error} - En cas d'échec. */ async function callApi(url, authToken = null, method = 'GET', data = null) { + // Définition des en-têtes de la requête const headers = { 'Content-Type': 'application/json' }; if (authToken) headers.Authorization = `Bearer ${authToken}`; + // Options de la requête fetch const fetchOptions = { method, headers }; log("Envoi de la requête vers :", url); + // Si des données sont fournies, les ajouter au corps de la requête if (data) { log("Body JSON :", JSON.stringify(data, null, 2)); fetchOptions.body = JSON.stringify(data); } try { + // Effectuer la requête fetch const response = await fetch(url, fetchOptions); + // Vérifier si la réponse est correcte if (!response.ok) { throw new Error(`⌠Erreur API (${response.status}): ${response.statusText}`); } + // Retourner la réponse en JSON return await response.json(); } catch (error) { log(`🚨 Erreur lors de l'appel API [${url}]:`, error); @@ -71,6 +78,9 @@ async function getLexicons(authToken) { /** * Récupère les entrées d'un lexique donné. + * @param {string} authToken - Le token d'authentification. + * @param {string} lexiconId - L'ID du lexique dont on veut récupérer les entrées. + * @returns {Promise<any[]>} - Liste des entrées du lexique. */ async function getLexiconEntries(authToken, lexiconId) { const url = `https://babalex.lezinter.net/api/lexicon/entries/${lexiconId}`; @@ -79,12 +89,15 @@ async function getLexiconEntries(authToken, lexiconId) { /** * Récupère toutes les graphies présentes dans tous les lexiques de l'utilisateur. + * @param {string} authToken - Le token d'authentification. + * @returns {Promise<object>} - Un objet contenant les graphies par lexique. */ async function getAllLexiconWords(authToken) { try { // 1) Récupération de la liste des lexiques const lexicons = await getLexicons(authToken); + // Vérification si la liste des lexiques est valide if (!Array.isArray(lexicons) || lexicons.length === 0) { console.warn("âš ï¸ Aucun lexique retourné par l'API pour ces paramètres."); return {}; @@ -94,12 +107,14 @@ async function getAllLexiconWords(authToken) { const allGraphiesByLexicon = {}; for (const lexicon of lexicons) { + // Récupération des entrées pour le lexique actuel 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; } + // Extraction des graphies des entrées const allGraphies = entries.map(entry => entry.graphy); // Création d'un libellé unique pour le lexique @@ -108,13 +123,14 @@ async function getAllLexiconWords(authToken) { ? `Lexique personnel (${lexicon.user?.pseudo || "Inconnu"}) [${lexicon.id}]` : `Lexique de groupe (${lexicon.group?.name || "Inconnu"}) [${lexicon.id}]`; + // Stockage des graphies par lexique allGraphiesByLexicon[lexiconName] = allGraphies; } - log("✅ Toutes les graphies récupérées :", allGraphiesByLexicon); + log("Toutes les graphies récupérées :", allGraphiesByLexicon); return allGraphiesByLexicon; } catch (error) { - log("⌠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 {}; } } @@ -122,25 +138,31 @@ async function getAllLexiconWords(authToken) { // ───────────────────────────────────────────────────────────────────────────── // â–Œ Récupération de définition du Wiktionnaire // ───────────────────────────────────────────────────────────────────────────── - /** * Récupère une définition du Wiktionnaire. + * @param {string} word - Le mot dont on veut la définition. + * @returns {Promise<string[]>} - Une promesse contenant la définition trouvée. */ async function getWiktionaryDefinition(word) { try { + // Construction de l'URL pour l'API du Wiktionnaire avec le mot spécifié const url = `https://fr.wiktionary.org/w/api.php?action=query&format=json&origin=*&prop=extracts&exintro=true&titles=${encodeURIComponent(word)}`; + // Envoi de la requête fetch à l'API const response = await fetch(url); if (!response.ok) { throw new Error(`Erreur API Wiktionnaire: ${response.statusText}`); } + // Traitement de la réponse JSON const data = await response.json(); const pages = data.query?.pages; + // Récupération de la première page de résultats const page = pages ? Object.values(pages)[0] : null; + // Extraction de la définition ou message par défaut si aucune définition n'est trouvée const definition = page?.extract?.trim() || "Aucune définition trouvée."; log(`📖 Définition trouvée pour '${word}':`, definition); - return [definition]; + return [definition]; // Retourner la définition sous forme de tableau } catch (error) { log("Erreur lors de la récupération du Wiktionnaire :", error); return ["Erreur : " + error.message]; @@ -170,6 +192,7 @@ async function AddWord(authToken, selectedWord, lexiconIds, force = false) { } const url = "https://babalex.lezinter.net/api/entry/create"; + // Corps de la requête contenant le mot et les lexiques cibles const body = { graphy: selectedWord, force, @@ -179,8 +202,6 @@ async function AddWord(authToken, selectedWord, lexiconIds, force = false) { return callApi(url, authToken, "POST", body); } - - // ───────────────────────────────────────────────────────────────────────────── // â–Œ Exposition des fonctions pour un usage global // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/utils/definitions.js b/src/utils/definitions.js index ca0a070fb98765545ac34919c9e4e1ac5aec243e..881ed5152af74d776f828132c832a4a737057147 100644 --- a/src/utils/definitions.js +++ b/src/utils/definitions.js @@ -1,19 +1,14 @@ // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Fonctions pour récupérer/afficher les définitions +// â–Œ Récupération des définitions (lexiques + Wiktionnaire) // ───────────────────────────────────────────────────────────────────────────── window.lexiconMap = new Map(); /** -* Récupère les définitions d'un mot dans les lexiques de l'utilisateur (ex. user_id=4), -* en effectuant un appel global à l'API pour le mot recherché, puis en filtrant -* les entrées pour ne conserver que celles dont le lexicon.id est présent dans la liste -* des lexiques de l'utilisateur. -* -* Retourne un tableau d'objets { source, text } où: -* - source = nom du lexique (ou "Lexique #ID") -* - text = texte de la définition -*/ + * Récupère les définitions d'un mot dans les lexiques de l'utilisateur. + * @param {string} word - Le mot dont on veut les définitions. + * @returns {Promise<object[]>} - Un tableau d'objets contenant les définitions. + */ async function fetchLexiconDefinitions(word) { try { log(`🔠Recherche des définitions de '${word}' dans les lexiques de l'utilisateur...`); @@ -48,7 +43,7 @@ async function fetchLexiconDefinitions(word) { : `Lexique de groupe : ${lex.group?.name || "Inconnu"}`; lexiconMap.set(lex.id, lexiconName); }); - log("📌 LexiconMap :", lexiconMap); + log("LexiconMap :", lexiconMap); // 2) Pour chaque lexique, rechercher le mot en ajoutant target_lex const definitionsPromises = userLexicons.map(async (lex) => { @@ -116,15 +111,17 @@ async function fetchLexiconDefinitions(word) { log("Résultat final filtré :", allDefinitions); return allDefinitions; } catch (error) { - log("⌠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 []; } } /** -* Récupère la définition d'un mot depuis le Wiktionnaire (fr). -* Retourne un tableau d'objets : [{ source: 'Wiktionnaire', text: '...' }] -*/ + * Récupère la définition d'un mot depuis le Wiktionnaire (fr). + * Retourne un tableau d'objets : [{ source: 'Wiktionnaire', text: '...' }] + * @param {string} word - Le mot dont on veut la définition. + * @returns {Promise<object[]>} - Un tableau d'objets contenant la définition. + */ async function fetchWiktionaryDefinition(word) { try { const result = await browser.storage.local.get("accessToken"); @@ -141,7 +138,7 @@ async function fetchWiktionaryDefinition(word) { throw new Error(`⌠Erreur API Wiktionnaire: ${response.statusText}`); } const data = await response.json(); - log("📖 Réponse API (Wiktionnaire) :", data); + log("Réponse API (Wiktionnaire) :", data); const pages = data.query?.pages; const page = pages ? Object.values(pages)[0] : null; @@ -150,7 +147,7 @@ async function fetchWiktionaryDefinition(word) { ? page.extract.trim() : "âš ï¸ Aucune définition trouvée sur le Wiktionnaire."; - log("🌠Définition Wiktionnaire extraite :", definitionText); + log("Définition Wiktionnaire extraite :", definitionText); return [ { @@ -182,11 +179,16 @@ async function fetchWiktionaryDefinition(word) { ]; } } catch (error) { - log("⌠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); return [{ source: "Wiktionnaire", text: "Erreur lors de la récupération sur le Wiktionnaire." }]; } } +/** + * Récupère la définition d'un mot depuis l'API de BaLex (Wiktionnaire). + * @param {string} word - Le mot dont on veut la définition. + * @returns {Promise<object[]>} - Un tableau d'objets contenant la réponse de l'API. + */ async function wikiApiResponse(word) { const result = await browser.storage.local.get("accessToken"); authToken = result.accessToken; @@ -216,6 +218,11 @@ async function wikiApiResponse(word) { } } +/** + * Formate les données de la réponse de l'API de BaLex (Wiktionnaire). + * @param {object[]} apiResponse - La réponse de l'API de BaLex (Wiktionnaire). + * @returns {object} - Un objet contenant les données formatées. + */ function formatDefinitionData(apiResponse) { let formattedData = { word: apiResponse[0]?.id.split("-").slice(2).join("-") || "", @@ -261,16 +268,17 @@ function formatDefinitionData(apiResponse) { } // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Affichage des définitions dans la barre latérale +// â–Œ Affichage des définitions dans la barre latérale (lexiques + Wiktionnaire) // ───────────────────────────────────────────────────────────────────────────── const MAX_LENGTH = 200; /** -* Affiche les définitions dans la barre latérale. -*/ + * Affiche les définitions dans la barre latérale. + * @param {object[]} definitions - Les définitions à afficher. + */ function displayDefinitions(definitions) { - log("📖 Affichage des définitions reçues :", definitions); + log("Affichage des définitions reçues :", definitions); if (!Array.isArray(definitions)) return; const mesLexiquesList = document.getElementById("mesLexiquesList"); @@ -371,42 +379,12 @@ function displayDefinitions(definitions) { } } -// ───────────────────────────────────────────────────────────────────────────── -// â–Œ Gestion du popup pour afficher la définition complète du Wiktionnaire -// ───────────────────────────────────────────────────────────────────────────── - -function openDefinitionPopup(fullText) { - const modalOverlay = document.getElementById("modalOverlay"); - const modalFullText = document.getElementById("modalFullText"); - if (!modalOverlay || !modalFullText) { - log("Modal elements not found!"); - return; - } - modalFullText.innerHTML = "<p>" + fullText.replace(/\n/g, "<br>") + "</p>"; - modalOverlay.style.display = "flex"; -} - -/** -* Ferme le popup et nettoie le contenu -*/ -function closeDefinitionPopup() { - const modalOverlay = document.getElementById("modalOverlay"); - const modalFullText = document.getElementById("modalFullText"); - if (!modalOverlay || !modalFullText) return; - modalOverlay.style.display = "none"; - modalFullText.innerHTML = ""; -} - -// ───────────────────────────────────────────────────────────────────────────── -// â–Œ Affichage des définitions Babalex + Wiktionnaire -// ───────────────────────────────────────────────────────────────────────────── - /** -* Récupère en parallèle : -* - les définitions des lexiques de l'utilisateur (fetchLexiconDefinitions) -* - la définition Wiktionnaire (fetchWiktionaryDefinition) -* Puis fusionne les résultats. -*/ + * Récupère en parallèle : + * - les définitions des lexiques de l'utilisateur (fetchLexiconDefinitions) + * - la définition Wiktionnaire (fetchWiktionaryDefinition) + * Puis fusionne les résultats. + */ async function combineDefinitions(word) { log(`[combineDefinitions] Récupération des définitions pour "${word}"...`); @@ -422,14 +400,15 @@ async function combineDefinitions(word) { const allDefinitions = [...lexiconDefinitions, ...wiktionaryDefinitions]; - log("📚 [combineDefinitions] Résultat fusionné :", allDefinitions); + log("[combineDefinitions] Résultat fusionné :", allDefinitions); return allDefinitions; } /** -* Récupère et affiche toutes les définitions (lexiques + Wiktionnaire). -*/ + * Récupère et affiche toutes les définitions (lexiques + Wiktionnaire). + * @param {string} word - Le mot dont on veut les définitions. + */ async function showDefinitions(word) { log(`[showDefinitions] Recherche + affichage pour "${word}"...`); @@ -459,11 +438,11 @@ async function showDefinitions(word) { return allDefinitions; } catch (error) { - log("⌠[showDefinitions] Erreur : ", error); + log("[showDefinitions] Erreur : ", error); if (noDefinitionsContainer) { noDefinitionsContainer.textContent = - "⌠Une erreur est survenue lors de la récupération des définitions."; + "Une erreur est survenue lors de la récupération des définitions."; noDefinitionsContainer.style.display = "block"; } return []; @@ -471,21 +450,22 @@ 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. + * @param {string} word - Le mot dont on veut la définition. + */ async function fetchDefinition(word) { log(`🔠Recherche de la définition pour '${word}'...`); const noDefinitionsContainer = document.getElementById("noDefinitionsContainer"); if (!noDefinitionsContainer) { - log("⌠Élément #noDefinitionsContainer introuvable."); + log("Élément #noDefinitionsContainer introuvable."); return; } try { const definition = await fetchLexiconDefinitions(word); - log("🔠Résultat API :", definition); + log("Résultat API :", definition); if (!definition || definition.length === 0) { console.warn(`âš ï¸ Aucune définition trouvée pour '${word}'`); @@ -495,13 +475,14 @@ async function fetchDefinition(word) { noDefinitionsContainer.style.display = "none"; } catch (error) { - log("⌠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"; } } /** -* Affiche les lexiques où le mot sélectionné est présent. + * Affiche les lexiques où le mot sélectionné est présent. + * @param {object[]} lexicons - Les lexiques où le mot est présent. */ function displayLexiconResults(lexicons) { const resultDiv = document.getElementById("lexiconResult"); @@ -510,7 +491,7 @@ function displayLexiconResults(lexicons) { resultDiv.innerHTML = ""; if (!lexicons || lexicons.length === 0) { - resultDiv.textContent = "⌠Ce mot n'est présent dans aucun lexique."; + resultDiv.textContent = "Ce mot n'est présent dans aucun lexique."; return; } const title = document.createElement("p"); @@ -543,15 +524,47 @@ function displayLexiconResults(lexicons) { } // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Utilisation des fonctions dans d'autres scripts +// â–Œ Gestion de la boîte modale pour afficher la définition complète du Wiktionnaire +// ───────────────────────────────────────────────────────────────────────────── +/** + * Ouvre la boîte modale pour afficher la définition complète du Wiktionnaire. + * @param {string} fullText - La définition complète du mot. + */ +function openDefinitionPopup(fullText) { + const modalOverlay = document.getElementById("modalOverlay"); + const modalFullText = document.getElementById("modalFullText"); + if (!modalOverlay || !modalFullText) { + log("Modal elements not found!"); + return; + } + modalFullText.innerHTML = "<p>" + fullText.replace(/\n/g, "<br>") + "</p>"; + modalOverlay.style.display = "flex"; +} + +/** + * Ferme la boîte modale et nettoie le contenu + * @returns {void} + */ +function closeDefinitionPopup() { + const modalOverlay = document.getElementById("modalOverlay"); + const modalFullText = document.getElementById("modalFullText"); + if (!modalOverlay || !modalFullText) return; + modalOverlay.style.display = "none"; + modalFullText.innerHTML = ""; +} + +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Exposition des fonctions pour un usage global // ───────────────────────────────────────────────────────────────────────────── window.fetchLexiconDefinitions = fetchLexiconDefinitions; window.fetchWiktionaryDefinition = fetchWiktionaryDefinition; +window.wikiApiResponse = wikiApiResponse; +window.formatDefinitionData = formatDefinitionData; window.displayDefinitions = displayDefinitions; -window.openDefinitionPopup = openDefinitionPopup; -window.closeDefinitionPopup = closeDefinitionPopup; window.combineDefinitions = combineDefinitions; window.showDefinitions = showDefinitions; window.fetchDefinition = fetchDefinition; window.displayLexiconResults = displayLexiconResults; +window.openDefinitionPopup = openDefinitionPopup; +window.closeDefinitionPopup = closeDefinitionPopup; diff --git a/src/utils/highlighting.js b/src/utils/highlighting.js index 4a673e9a1849f7f040d23b055fd09f39948b448e..1851fda0fdb44aac5f4f59bb3d4aee12d48209c0 100644 --- a/src/utils/highlighting.js +++ b/src/utils/highlighting.js @@ -1,55 +1,87 @@ -// Variables globales +// ───────────────────────────────────────────────────────────── +// â–Œ Configuration initiale, logs et gestion des erreurs +// ───────────────────────────────────────────────────────────── +log("highlighting.js chargé."); 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:", { +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 }); + log("Erreur globale:", { message: msg, url: url, line: line, col: col, error: error }); return false; }; -// Fonction d'initialisation du token depuis le stockage local +// ───────────────────────────────────────────────────────────── +// â–Œ Authentification et gestion du token +// ───────────────────────────────────────────────────────────── +/** + * Récupère le token d'authentification depuis le stockage local. + * @returns {Promise<boolean>} true si le token est récupéré, false sinon. + */ 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"); + 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); + log("Erreur lors de la récupération du token:", error); return false; } } initAuthToken(); -// Écoute de mise à jour du token +// Mise à jour du token via messages du background 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); + log("Token mis à jour via message:", !!message.token); + } +}); + +// ───────────────────────────────────────────────────────────── +// â–Œ Gestion des événements globaux +// ───────────────────────────────────────────────────────────── +// Réinitialisation du surlignage lors du retour sur la page +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(); } }); +// Enregistrement du script auprès du background +log("Enregistrement du script auprès du background"); +browser.runtime.sendMessage({ command: "register-highlighting-script" }); +log("Initialisation de highlighting.js"); + +// ───────────────────────────────────────────────────────────── +// â–Œ Logique de surlignage et gestion des mutations DOM +// ───────────────────────────────────────────────────────────── (function () { try { if (window.hasRun) { @@ -64,160 +96,236 @@ browser.runtime.onMessage.addListener((message) => { 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" }); + // ─────────────────────────────── + // â–Œ Fonctions utilitaires + // ─────────────────────────────── + /** + * Échappe les caractères spéciaux pour utilisation dans une expression régulière + * @param {string} string - La chaîne à échapper + * @returns {string} La chaîne échappée + */ + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } - log("🚀 Initialisation de highlighting.js"); + /** + * Extrait l'ID d'un lexique depuis son nom + * @param {string} lexiconName - Le nom du lexique + * @returns {number|null} L'ID du lexique ou null si l'extraction échoue + */ + 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; + } - // Fonction asynchrone pour mettre à jour le style d'un élément surligné en fonction des lexiques qui le concernent + // ─────────────────────────────── + // â–Œ Gestion du style de surlignage + // ─────────────────────────────── + /** + * Met à jour le style du surlignage pour un élément spécifié + * @param {Element} span - L'élément à surligner + * @param {Array} lexIds - Les identifiants des lexiques à surligner + * @returns {Promise<void>} + */ async function updateHighlightStyle(span, lexIds) { if (!lexIds || lexIds.length === 0) { + // Définit une couleur de fond par défaut si aucun lexique n'est actif span.style.backgroundColor = "rgba(255, 255, 0, 0.3)"; span.style.backgroundImage = ""; return; } + // Si un seul lexique est actif if (lexIds.length === 1) { + // Récupère la couleur hexadécimale pour le lexique const hexColor = await getColorForLexicon(lexIds[0]); const rgbaColor = hexToRgba(hexColor, 0.3); span.style.backgroundColor = rgbaColor; span.style.backgroundImage = ""; } else { + // Si plusieurs lexiques sont actifs, récupère les couleurs pour chacun 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 = []; + const n = colors.length; // Nombre de couleurs + let stops = []; // Tableau pour stocker les arrêts de dégradé for (let i = 0; i < n; i++) { + // Calcule les positions de début et de fin pour chaque couleur dans le dégradé const start = ((100 * i) / n).toFixed(2) + '%'; const end = ((100 * (i + 1)) / n).toFixed(2) + '%'; stops.push(`${colors[i]} ${start}, ${colors[i]} ${end}`); } + // Crée un dégradé linéaire avec les couleurs calculées const gradient = `linear-gradient(90deg, ${stops.join(', ')})`; - span.style.backgroundImage = gradient; - + span.style.backgroundImage = gradient; // Applique le dégradé comme image de fond } } - // Vérifier si un mot appartient à un lexique (à l'aide de la cache) + // ─────────────────────────────── + // â–Œ Gestion du cache des lexiques + // ─────────────────────────────── + /** + * Met à jour le cache des lexiques + * @returns {Promise<boolean>} true si la mise à jour est réussie, false sinon + */ + async function updateLexiconCache() { + log("updateLexiconCache - Début avec contexte:", { + 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..."); + // Appelle la fonction pour récupérer tous les mots de lexique + 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; + } + // Traite chaque lexique reçu + for (const [lexiconName, words] of Object.entries(allWords)) { + if (!Array.isArray(words)) { + console.warn(`âš ï¸ Format invalide pour le lexique ${lexiconName}:`, words); + continue; + } + // Extrait l'ID du lexique + 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})`); + // Si l'ID du lexique est actif, met à jour le cache + 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; + } + } + + // ─────────────────────────────── + // â–Œ Vérification d'appartenance d'un mot à un lexique + // ─────────────────────────────── + /** + * Vérifie si un mot appartient à un lexique spécifié + * @param {string} lexiconId - L'identifiant du lexique + * @param {string} word - Le mot à vérifier + * @returns {boolean} true si le mot appartient au lexique, false sinon + */ 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é + // ─────────────────────────────── + // â–Œ Mise à jour incrémentale des surlignages pour un nouveau lexique + // ─────────────────────────────── + /** + * Met à jour les surlignages pour un nouveau lexique + * @param {string} newLexiconId - L'identifiant du nouveau lexique + * @returns {Promise<void>} + */ async function updateHighlightsForNewLexicon(newLexiconId) { const spans = document.querySelectorAll('.lexicon-highlight'); + // Parcourt chaque élément surligné for (let span of spans) { - const word = span.textContent; + const word = span.textContent; + // Vérifie si le mot appartient au nouveau lexique if (wordIsInLexicon(newLexiconId, word)) { let lexIds = []; try { lexIds = JSON.parse(span.getAttribute('data-lexicons')); } catch (e) { - lexIds = []; + lexIds = []; // Si une erreur se produit, initialise lexIds comme un tableau vide } + // Vérifie si le nouvel identifiant de lexique n'est pas déjà présent if (!lexIds.includes(newLexiconId)) { - lexIds.push(newLexiconId); + lexIds.push(newLexiconId); // Ajoute le nouvel identifiant de lexique span.setAttribute('data-lexicons', JSON.stringify(lexIds)); + // Met à jour le style de surlignage pour l'élément await updateHighlightStyle(span, lexIds); } } } } - // Fonction startHighlighting modifiée pour intégrer la mise à jour incrémentale + // ─────────────────────────────── + // â–Œ Fonctions principales : démarrage et arrêt du surlignage + // ─────────────────────────────── + /** + * Démarre le surlignage pour un lexique spécifié + * @param {string} lexiconId - L'identifiant du lexique à surligner + * @returns {Promise<boolean>} true si le surlignage est démarré, false sinon + */ async function startHighlighting(lexiconId) { try { await initAuthToken(); if (!window.authToken) { throw new Error("⌠Pas de token d'authentification disponible"); } + // Vérifie si un identifiant de lexique a été fourni if (lexiconId) { + // Vérifie si le lexique n'est pas déjà actif if (!activeLexiconIds.has(lexiconId)) { activeLexiconIds.add(lexiconId); const activeLexicons = Array.from(activeLexiconIds); + // Sauvegarde les lexiques actifs dans le stockage local await browser.storage.local.set({ activeLexicons }); - log("📊 Lexiques actifs sauvegardés:", 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 + // Mise à jour immédiate des éléments surlignés await updateHighlightsForNewLexicon(lexiconId); } } + // Active le surlignage window.highlightingActive = true; highlightingActive = true; highlightVisibleContent(); + // Attache un observateur de mutations pour surveiller les changements dans le DOM attachMutationObserver(); return true; } catch (error) { - log("⌠Erreur lors du démarrage du surlignage:", error); + log("Erreur lors du démarrage du surlignage:", error); window.highlightingActive = false; highlightingActive = false; throw error; } } + /** + * Arrête le surlignage pour un lexique spécifié + * @param {string} lexiconId - L'identifiant du lexique à arrêter + * @returns {Promise<boolean>} true si l'arrêt est réussi, false sinon + */ async function stopHighlighting(lexiconId) { try { if (lexiconId) { @@ -243,87 +351,35 @@ browser.runtime.onMessage.addListener((message) => { } 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 - }); + log("Erreur lors de l'arrêt du surlignage:", error); throw error; } } - // Surlignage du contenu visible + // ─────────────────────────────── + // â–Œ Surlignage du contenu visible + // ─────────────────────────────── + /** + * Surligne le contenu visible de la page + */ function highlightVisibleContent() { + // Vérifie si le surlignage est actif 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 = []; + log("Début du surlignage du contenu visible"); + const BATCH_SIZE = 100; // Nombre d'éléments à traiter par lot + const BATCH_DELAY = 10; // Délai entre les traitements de lots en millisecondes + const textNodes = []; // Tableau pour stocker les nÅ“uds de texte trouvés + // Crée un TreeWalker pour parcourir les nÅ“uds de texte dans le document const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { const parent = node.parentElement; + // Vérifie si le nÅ“ud doit être accepté ou rejeté if (!parent || parent.closest('script, style, .lexicon-highlight') || !node.textContent.trim() || getComputedStyle(parent).display === 'none') { return NodeFilter.FILTER_REJECT; } @@ -332,45 +388,60 @@ browser.runtime.onMessage.addListener((message) => { } ); let node; + // Parcourt tous les nÅ“uds de texte et les ajoute au tableau while (node = walker.nextNode()) { textNodes.push(node); } log(`🔤 ${textNodes.length} nÅ“uds de texte trouvés à traiter`); + // Fonction pour traiter le prochain lot de nÅ“uds de texte const processNextBatch = (startIndex) => { + // Vérifie si le surlignage est toujours actif et si des nÅ“uds restent à traiter if (!highlightingActive || startIndex >= textNodes.length) { - return; + return; // Sort de la fonction si le surlignage n'est pas actif ou si tous les nÅ“uds ont été traités } - const endIndex = Math.min(startIndex + BATCH_SIZE, textNodes.length); - const batch = textNodes.slice(startIndex, endIndex); - batch.forEach(processTextNode); + const endIndex = Math.min(startIndex + BATCH_SIZE, textNodes.length); // Calcule l'index de fin du lot + const batch = textNodes.slice(startIndex, endIndex); // Extrait le lot de nÅ“uds à traiter + batch.forEach(processTextNode); // Traite chaque nÅ“ud du lot + // Si des nÅ“uds restent à traiter, planifie le traitement du prochain lot if (endIndex < textNodes.length) { setTimeout(() => processNextBatch(endIndex), BATCH_DELAY); } }; - processNextBatch(0); + processNextBatch(0); // Démarre le traitement à partir du premier nÅ“ud } - // Traitement d'un nÅ“ud de texte + // ─────────────────────────────── + // â–Œ Traitement d'un "nÅ“ud de texte" pour le surlignage + // ─────────────────────────────── + /** + * Traite un "nÅ“ud de texte" pour le surlignage + * @param {Node} textNode - Le nÅ“ud de texte à traiter + */ 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(); + const text = textNode.textContent; // Récupère le texte du nÅ“ud + log(`🔠Traitement du texte: "${text.substring(0, 50)}..."`); + let lastIndex = 0; // Index de la dernière correspondance trouvée + let fragments = []; // Tableau pour stocker les parties de texte + const allWords = new Set(); // Ensemble pour stocker tous les mots à rechercher + const matchedLexiconIdsMap = new Map(); // Map pour associer les mots aux identifiants de lexiques + + // Parcourt chaque lexique dans le cache for (const [lexiconId, words] of lexiconWordsCache.entries()) { - const numericId = parseInt(lexiconId); + const numericId = parseInt(lexiconId); // Convertit l'ID du lexique en nombre log(`🔄 Vérification du lexique ${lexiconId} (ID: ${numericId})`); + // Vérifie si le lexique est actif if (activeLexiconIds.has(numericId)) { log(`✅ Lexique ${lexiconId} actif, ajout de ${words.size} mots`); + // Ajoute chaque mot à l'ensemble des mots à rechercher words.forEach(word => allWords.add(word)); + // Associe chaque mot à son identifiant de lexique words.forEach(word => { const lowerCaseWord = word.toLowerCase(); - if (!matchedLexiconIdsMap.has(lowerCaseWord)) { // Correction ici ! + if (!matchedLexiconIdsMap.has(lowerCaseWord)) { matchedLexiconIdsMap.set(lowerCaseWord, []); } matchedLexiconIdsMap.get(lowerCaseWord).push(lexiconId); @@ -378,23 +449,28 @@ browser.runtime.onMessage.addListener((message) => { } } log(`🔤 Nombre total de mots à rechercher: ${allWords.size}`); + // Vérifie si des mots sont disponibles pour la recherche if (allWords.size === 0) { log("âš ï¸ Aucun mot à rechercher dans les lexiques actifs"); return; } + // Crée une expression régulière pour rechercher les mots const wordsPattern = Array.from(allWords) - .sort((a, b) => b.length - a.length) - .map(escapeRegExp) - .join("|"); + .sort((a, b) => b.length - a.length) // Trie les mots par longueur décroissante + .map(escapeRegExp) // Échappe les caractères spéciaux + .join("|"); // Joint les mots avec un séparateur "ou" + // Vérifie si le motif de recherche est valide if (!wordsPattern) { log("âš ï¸ Aucun mot à rechercher, sortie"); return; } - const regex = new RegExp(`\\b(${wordsPattern})\\b`, "gi"); - let match; - let matchCount = 0; + const regex = new RegExp(`\\b(${wordsPattern})\\b`, "gi"); // Crée l'expression régulière + let match; // Variable pour stocker les correspondances + let matchCount = 0; // Compteur de correspondances trouvées + // Recherche les correspondances dans le texte while ((match = regex.exec(text)) !== null) { - matchCount++; + matchCount++; // Incrémente le compteur de correspondances + // Ajoute le texte avant la correspondance au tableau de parties if (match.index > lastIndex) { fragments.push(document.createTextNode(text.slice(lastIndex, match.index))); } @@ -403,33 +479,42 @@ browser.runtime.onMessage.addListener((message) => { span.className = "lexicon-highlight"; span.style.display = "inline-block"; + // Récupère les identifiants de lexiques associés à la correspondance const matchedLexiconIds = matchedLexiconIdsMap.get(match[0].toLowerCase()) || []; span.setAttribute('data-lexicons', JSON.stringify(matchedLexiconIds)); + // Applique le style de surlignage if (matchedLexiconIds.length === 0) { span.style.backgroundColor = "rgba(255, 255, 0, 0.3)"; } else { updateHighlightStyle(span, matchedLexiconIds); } - fragments.push(span); - lastIndex = regex.lastIndex; + fragments.push(span); // Ajoute le span au tableau de parties + lastIndex = regex.lastIndex; // Met à jour l'index de la dernière correspondance } if (matchCount > 0) { log(`✨ ${matchCount} correspondances trouvées dans le nÅ“ud`); } + // Ajoute le texte restant après la dernière correspondance if (lastIndex < text.length) { fragments.push(document.createTextNode(text.slice(lastIndex))); } + // Insère les parties dans le DOM if (fragments.length > 0) { - const parent = textNode.parentNode; - fragments.forEach(fragment => parent.insertBefore(fragment, textNode)); - parent.removeChild(textNode); + const parent = textNode.parentNode; // Récupère le parent du nÅ“ud de texte + fragments.forEach(fragment => parent.insertBefore(fragment, textNode)); // Insère chaque partie avant le nÅ“ud de texte + parent.removeChild(textNode); // Supprime le nÅ“ud de texte original } } - // Suppression de tous les surlignages + // ─────────────────────────────── + // â–Œ Suppression de tous les surlignages + // ─────────────────────────────── + /** + * Supprime tous les surlignages de la page + */ function removeAllHighlights() { - log("🧹 Suppression de tous les surlignages"); + log("Suppression de tous les surlignages"); const highlights = document.querySelectorAll('.lexicon-highlight'); log(`📊 ${highlights.length} surlignages à supprimer`); highlights.forEach(highlight => { @@ -439,31 +524,41 @@ browser.runtime.onMessage.addListener((message) => { }); } - // Gestion des mutations DOM + // ─────────────────────────────── + // â–Œ Gestion des mutations DOM + // ─────────────────────────────── + /** + * Attache un observateur de mutations DOM + */ function attachMutationObserver() { - log("👀 Attachement de l'observateur de mutations"); + log("Attachement de l'observateur de mutations"); let debounceTimer = null; const DEBOUNCE_DELAY = 250; // ms observer = new MutationObserver((mutations) => { if (debounceTimer) { clearTimeout(debounceTimer); } + // Définit un nouveau timer debounceTimer = setTimeout(() => { log(`🔄 Traitement groupé de ${mutations.length} mutations DOM`); let shouldHighlight = false; for (const mutation of mutations) { + // Vérifie si la mutation concerne des nÅ“uds enfants if (mutation.type === 'childList') { + // Parcourt les nÅ“uds ajoutés for (const node of mutation.addedNodes) { + // Vérifie si le nÅ“ud est un élément et n'est pas déjà surligné if (node.nodeType === Node.ELEMENT_NODE && !node.closest('.lexicon-highlight') && node.textContent.trim()) { - shouldHighlight = true; - break; + shouldHighlight = true; // Indique qu'un surlignage est nécessaire + break; // Sort de la boucle si un nÅ“ud à surligner est trouvé } } } if (shouldHighlight) break; } + // Si un surlignage est nécessaire, appelle la fonction pour surligner le contenu visible if (shouldHighlight) { highlightVisibleContent(); } @@ -474,53 +569,97 @@ browser.runtime.onMessage.addListener((message) => { subtree: true }); } - + /** + * Détache un observateur de mutations DOM + */ function detachMutationObserver() { if (observer) { - log("👋 Détachement de l'observateur de mutations"); + 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 + // ─────────────────────────────── + // â–Œ Gestion des messages 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 + }); + + // Vérifie si la commande est pour activer le surlignage + if (message.command === "activate-highlighting") { + log(`🎯 Activation du surlignage pour le lexique ${message.lexiconId}`); + // Démarre le surlignage pour le lexique spécifié + startHighlighting(message.lexiconId) + .then(() => { + window.highlightingActive = true; // Met à jour l'état du surlignage + sendResponse(true); // Envoie une réponse de succès + }) + .catch(error => { + log("Erreur lors de l'activation:", error); + sendResponse(false); + }); + return true; + } + + // Vérifie si la commande est pour désactiver le surlignage + if (message.command === "deactivate-highlighting") { + log(`🚫 Désactivation du surlignage pour le lexique ${message.lexiconId}`); + // Arrête le surlignage pour le lexique spécifié + stopHighlighting(message.lexiconId) + .then(() => { + // Vérifie si aucun lexique n'est actif + 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; }); + // ─────────────────────────────── + // â–Œ Restauration de l'état du surlignage au chargement + // ─────────────────────────────── + /** + * Vérifie et restaure l'état du surlignage au chargement + */ async function checkAndRestoreHighlightingState() { try { + // Récupère les lexiques actifs depuis le stockage local const { activeLexicons } = await browser.storage.local.get("activeLexicons"); + // Vérifie si des lexiques actifs ont été trouvés if (!activeLexicons || !Array.isArray(activeLexicons) || activeLexicons.length === 0) { - window.highlightingActive = false; - highlightingActive = false; - return; + window.highlightingActive = false; // Désactive le surlignage + highlightingActive = false; // Met à jour l'état local + return; // Sort de la fonction si aucun lexique actif } - log("🔄 État des lexiques trouvé:", activeLexicons); + log("État des lexiques trouvé:", activeLexicons); for (const lexiconId of activeLexicons) { - await startHighlighting(lexiconId); + await startHighlighting(lexiconId); // Démarre le surlignage pour chaque lexique } } catch (error) { - log("⌠Erreur lors de la restauration de l'état:", error); + log("Erreur lors de la restauration de l'état:", error); window.highlightingActive = false; highlightingActive = false; } } + // Démarrage initial de la restauration de l'état checkAndRestoreHighlightingState(); } catch (error) { - log("🔴 Erreur critique dans l'IIFE:", error); + log("Erreur critique dans l'IIFE:", error); } })(); diff --git a/src/utils/logger.js b/src/utils/logger.js index 3d3891c61284b17268311004cb1246ab881987da..15d163bac1c525548dc3e013ae8bb6065c9bcfdf 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -1,11 +1,15 @@ /** - * Mode debug : affiche les logs dans la console - * Mode prod : masque les logs ainsi que les identifiants des lexiques -*/ + * Ce script définit une fonction de log qui s'active en mode debug. + * Si la variable DEBUG est définie sur true, les messages de log seront affichés dans la console. + * Sinon, ils seront masqués. + * En mode debug, les identifiants (id) des lexiques de l'utilisateur sont affichés. + */ + (function () { + // Vérifie si le code s'exécute dans un environnement de navigateur if (typeof window !== 'undefined') { if (typeof window.DEBUG === 'undefined') { - window.DEBUG = true; // true en debug + window.DEBUG = true; // true en mode debug } if (!window.log) { function log(...args) { @@ -13,11 +17,13 @@ console.log(...args); } } - window.log = log; + window.log = log; // Assigne la fonction log à l'objet window } - } else if (typeof self !== 'undefined') { + } + // Vérifie si le code s'exécute dans un environnement worker + else if (typeof self !== 'undefined') { if (typeof self.DEBUG === 'undefined') { - self.DEBUG = true; // true en debug + self.DEBUG = true; // true en mode debug } if (!self.log) { function log(...args) { @@ -25,7 +31,7 @@ console.log(...args); } } - self.log = log; + self.log = log; // Assigne la fonction log à l'objet self } } })(); diff --git a/src/workers/pyodide_worker.js b/src/workers/pyodide_worker.js index 3e8458ee8f68dc9be5e0bc751f4d9c30c645974d..469b419bd92c1bbd643390131deec98763ec0722 100644 --- a/src/workers/pyodide_worker.js +++ b/src/workers/pyodide_worker.js @@ -1,54 +1,49 @@ importScripts("../utils/logger.js"); -log("✅ pyodide_worker.js chargé avec succès !"); +log("pyodide_worker.js chargé avec succès !"); -// URL de la version Pyodide la plus récente +// ───────────────────────────────────────────────────────────── +// â–Œ Constantes et variables globales +// ───────────────────────────────────────────────────────────── const LATEST_BASE_URL = "https://cdn.jsdelivr.net/pyodide/v0.27.2/full/"; let pyodide = null; -let pyodideLoaded = false; // Indique si Pyodide est en mémoire -let simplemmaLoaded = false; // Indique si simplemma est déjà installé +let pyodideLoaded = false; // Indique si Pyodide est chargé +let simplemmaLoaded = false; // Indique si simplemma est installé let storedFrequencies = {}; // Stockage des fréquences accumulées +// Préférences et configuration utilisateur let autoAddEnabled = false; // Ajout automatique désactivé par défaut let isAuthenticated = false; // Non connecté par défaut let userThreshold = 10; // Seuil par défaut let trackedLanguages = []; // Aucune langue suivie par défaut -let notifiedWords = {}; // Stockage des mots déjà signalés (pour éviter les doublons) -let stoplistFr = new Set(); // Stockage optimisé de la stoplist - -// Écouteur des messages reçus du background script -self.onmessage = async (event) => { - const data = event.data; - log("[WebWorker] Message reçu du Background:", data); - - if (data.command === "pyodide-simplemma") { - if (pyodideLoaded && simplemmaLoaded) { - 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; - } +let notifiedWords = {}; // Mots déjà notifiés (pour éviter les doublons) +let stoplistFr = new Set(); // Stoplist pour le français + +// ───────────────────────────────────────────────────────────── +// â–Œ Fonction de chargement de Pyodide et installation de simplemma +// ───────────────────────────────────────────────────────────── +async function loadPyodideAndSimplemma() { + // Chargement de Pyodide + if (!pyodideLoaded) { + log("[Worker] Chargement de Pyodide..."); try { - if (!pyodideLoaded) { - log("[Worker] Chargement de Pyodide..."); - try { - importScripts(`${LATEST_BASE_URL}pyodide.js`); - pyodide = await loadPyodide({ indexURL: LATEST_BASE_URL }); - await pyodide.loadPackage("lzma"); - await pyodide.loadPackage("micropip"); - pyodideLoaded = true; - } catch (err) { - log("[Worker] Erreur lors de l'import de pyodide.js :", err); - self.postMessage({ type: "pyodide-simplemma", status: "error", message: err.toString() }); - return; - } - log("[Worker] Pyodide chargé avec succès !"); - } - - if (!simplemmaLoaded) { - log("[Worker] Installation de simplemma..."); + importScripts(`${LATEST_BASE_URL}pyodide.js`); + pyodide = await loadPyodide({ indexURL: LATEST_BASE_URL }); + await pyodide.loadPackage("lzma"); + await pyodide.loadPackage("micropip"); + pyodideLoaded = true; + log("[Worker] Pyodide chargé avec succès !"); + } catch (err) { + log("[Worker] Erreur lors de l'import de pyodide.js :", err); + throw err; + } + } - // On encapsule la logique dans une fonction asynchrone pour faciliter l'usage d'await - await pyodide.runPythonAsync(` + // Installation de simplemma + if (!simplemmaLoaded) { + log("[Worker] Installation de simplemma..."); + try { + await pyodide.runPythonAsync(` import micropip import asyncio @@ -70,24 +65,66 @@ async def main(): return lemmatized_tokens await main() - `); - simplemmaLoaded = true; - 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" }); + `); + simplemmaLoaded = true; + log("[Worker] Simplemma installé avec succès !"); + } catch (err) { + log("[Worker] Erreur lors de l'installation de simplemma :", err); + throw err; + } + } +} + +// ───────────────────────────────────────────────────────────── +// â–Œ Gestion des messages reçus du background +// ───────────────────────────────────────────────────────────── +self.onmessage = async (event) => { + const data = event.data; + log("[WebWorker] Message reçu du Background:", data); + + // ─────────────── + // Commande : Chargement de Pyodide et Simplemma + // ─────────────── + if (data.command === "pyodide-simplemma") { + if (pyodideLoaded && simplemmaLoaded) { + 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 { + await loadPyodideAndSimplemma(); + self.postMessage({ + type: "pyodide-simplemma", + status: "success", + message: "Pyodide et Simplemma chargés" + }); } catch (error) { log("[Worker] Erreur lors du chargement de Pyodide ou Simplemma :", error); - self.postMessage({ type: "pyodide-simplemma", status: "error", message: error.toString() }); + self.postMessage({ + type: "pyodide-simplemma", + status: "error", + message: error.toString() + }); } + return; } - // --- Traitement du texte envoyé par stats.js --- + // ─────────────── + // Commande : Traitement du texte + // ─────────────── if (data.command === "process-text") { if (!pyodideLoaded || !simplemmaLoaded) { - const errorMessage = "Les statistiques ne sont pas encore activées. Veuillez patienter..." + const errorMessage = "Les statistiques ne sont pas encore activées. Veuillez patienter..."; log("[Worker] Pyodide non chargé."); - self.postMessage({ type: "process-text", status: "error", message: errorMessage }); + self.postMessage({ + type: "process-text", + status: "error", + message: errorMessage + }); return; } log("[Worker] Texte reçu pour analyse :", data.text); @@ -136,30 +173,44 @@ json.dumps({"lang": detected_lang, "frequencies": freq}, ensure_ascii=False) for (const [word, count] of Object.entries(parsedResult.frequencies)) { storedFrequencies[detectedLang][word] = (storedFrequencies[detectedLang][word] || 0) + count; } - self.postMessage({ type: "update-frequencies", frequencies: storedFrequencies }); + self.postMessage({ + type: "update-frequencies", + frequencies: storedFrequencies + }); if (autoAddEnabled) { checkThreshold(detectedLang); } } catch (error) { log("[Worker] Erreur dans l'analyse du texte :", error); } + return; } + // ─────────────── + // Commande : Mise à jour des préférences utilisateur + // ─────────────── if (data.command === "update-preferences") { userThreshold = data.threshold; trackedLanguages = data.trackedLanguages; autoAddEnabled = data.autoAdd; isAuthenticated = data.isAuthenticated; log("[Worker] Mise à jour des préférences :", { userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated }); + return; } + // ─────────────── + // Commande : Mise à jour de la stoplist + // ─────────────── if (data.command === "update-stoplist") { stoplistFr = new Set(data.stoplist.map(word => word.toLowerCase().trim())); log("[Worker] Stoplist FR mise à jour :", stoplistFr); + return; } }; -// --- Vérification du seuil et notification --- +// ───────────────────────────────────────────────────────────── +// â–Œ Vérification du seuil et notification +// ───────────────────────────────────────────────────────────── function checkThreshold(lang) { if (!autoAddEnabled || !isAuthenticated) { log("[Worker] Auto-Add désactivé ou utilisateur non connecté. Aucune vérification de seuil."); @@ -173,7 +224,8 @@ function checkThreshold(lang) { let wordsAboveThreshold = {}; if (storedFrequencies[lang]) { const exceededWords = Object.entries(storedFrequencies[lang]) - .filter(([word, count]) => count >= userThreshold && !(notifiedWords[lang] && notifiedWords[lang].includes(word))) + .filter(([word, count]) => count >= userThreshold && + !(notifiedWords[lang] && notifiedWords[lang].includes(word))) .map(([word]) => word); if (exceededWords.length > 0) { if (!notifiedWords[lang]) { @@ -185,7 +237,10 @@ function checkThreshold(lang) { } if (Object.keys(wordsAboveThreshold).length > 0) { log("[Worker] Nouveaux mots dépassant le seuil :", wordsAboveThreshold); - self.postMessage({ type: "threshold-exceeded", wordsAboveThreshold: wordsAboveThreshold }); + self.postMessage({ + type: "threshold-exceeded", + wordsAboveThreshold: wordsAboveThreshold + }); } else { log("[Worker] Aucun nouveau mot n'a dépassé le seuil."); }