diff --git a/manifest.json b/manifest.json index d465f43555b8ab019dc62e2f4ffae2a8cba59b1c..5817a844c640730cf92f218f1a8d4ba04e8302a2 100644 --- a/manifest.json +++ b/manifest.json @@ -31,18 +31,13 @@ "browser_action": { "default_area": "navbar", - "default_popup": "src/popup/popup.html", + "default_popup": "src/plugin/plugin.html", "default_icon": { "16": "src/assets/icons/logo.png" }, "default_title": "ff2BaLex" }, - "options_ui": { - "page": "src/options/options.html", - "open_in_tab": false - }, - "sidebar_action": { "default_title": "BaLex", "default_panel": "src/sidebar/sidebar.html", @@ -61,14 +56,28 @@ "src/utils/definitions.js", "src/sidebar/sidebar.js", "src/context_menu/custom_context_menu.js", - "src/utils/stats.js"], - "css": ["src/context_menu/custom_context_menu.css"], + "src/utils/stats.js", + "src/utils/highlighting.js" + ], + "css": ["src/css/custom_context_menu.css"], "run_at": "document_idle" }, { "matches": ["<all_urls>"], - "js": ["src/utils/stats.js"], + "js": [ + "src/utils/logger.js", + "src/utils/stats.js" + ], "run_at": "document_end" + }, + { + "matches": ["<all_urls>"], + "js": [ + "src/utils/logger.js", + "src/utils/api.js", + "src/utils/highlighting.js" + ], + "run_at": "document_start" } ], 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 b41da8b821d6f3034254aa6808c98b90c8b74337..2ad660a35280c702f3baba04c7e748145894c6d9 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -1,55 +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 }); + 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, @@ -65,19 +70,28 @@ 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; } -async function refreshAllUI() { - log("🔄 Rafraîchissement global de l'UI..."); - try { - await browser.runtime.sendMessage({ action: "refreshUI" }); - } catch (error) { - console.warn("Aucun récepteur pour 'refreshUI' :", error); +/** + * 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..."); + try { + await browser.runtime.sendMessage({ action: "refreshUI" }); + } catch (error) { + console.warn("Aucun récepteur pour 'refreshUI' :", error); + } } -} // ───────────────────────────────────────────────────────────────────────────── // Fonctions d'authentification & de redirection @@ -86,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, @@ -132,37 +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 }); + 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, @@ -173,136 +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 { - // S'assurer que le script est injecté - await browser.scripting.executeScript({ - target: { tabId: tabs[0].id }, - files: ["src/utils/highlighting.js"] - }); - - // Envoyer le message d'activation - await browser.tabs.sendMessage(tabs[0].id, { - command: message.isActive ? "activate-highlighting" : "deactivate-highlighting", - lexiconId: message.lexiconId - }); - log(`✅ Message de surlignage transmis à l'onglet ${tabs[0].id}`); - } catch (error) { - log("⌠Erreur lors de la gestion du surlignage:", error); - } - } - break; - } - - case "register-highlighting-script": { - log("📠Script de surlignage enregistré pour l'onglet", sender.tab.id); - break; - } - - default: - break; - } - 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 }; } }, @@ -310,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: ` @@ -362,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..."); @@ -382,40 +441,49 @@ 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); - } else if (data.status === "already_loaded") { - log("[Background] Pyodide et Simplemma déjà chargés."); + log("[Background] Erreur lors du chargement :", data.message); + } else if (data.status === "already_loaded") { + log("[Background] Pyodide et Simplemma déjà chargés."); } break; case "update-frequencies": 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; - case "word-added": - log(`[Background] Mot ajouté : '${data.word}' dans '${data.language}' (Lexiques: ${data.lexicons})`); - break; default: console.warn("[Background] Message non traité du Worker :", data); break; @@ -426,11 +494,11 @@ function handleWorkerMessage(event) { initWorker(); // ───────────────────────────────────────────────────────────────────────────── -// Écoute des messages de la popup et transmission au WebWorker +// Écoute des messages de l'extension et transmission au WebWorker // ───────────────────────────────────────────────────────────────────────────── 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(); } @@ -438,15 +506,15 @@ 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 }); - // 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" }); } @@ -543,7 +611,7 @@ async function loadStoplistsForLanguages(languages) { const response = await fetch(browser.runtime.getURL(stoplistPath)); const text = await response.text(); stoplists[lang] = text.split("\n").map(word => word.trim()); - log(`[Background] ✅ Stoplist chargée pour '${lang}' : ${stoplists[lang].length} mots`); + log(`[Background] Stoplist chargée pour '${lang}' : ${stoplists[lang].length} mots`); } catch (error) { console.warn(`[Background] âš Stoplist introuvable pour '${lang}', aucun filtrage ne sera appliqué.`); } @@ -564,6 +632,10 @@ browser.runtime.onInstalled.addListener(sendLexiconsToWorker); // ───────────────────────────────────────────────────────────────────────────── // 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 || {}; @@ -579,6 +651,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) { @@ -593,6 +670,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) => { @@ -606,15 +687,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."); - const { accessToken, trackedLanguages, threshold, autoAdd} = await browser.storage.local.get([ + // 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, @@ -624,6 +712,7 @@ browser.storage.onChanged.addListener(async (changes, area) => { }); } + //Écoute sur le bouton d'inclusion des mots outils if (area === "local" && changes.includeStopwords) { const includeStopwords = changes.includeStopwords.newValue; log(`[Background] Inclusion des mots outils activé/désactivé: ${includeStopwords}`); @@ -673,10 +762,36 @@ browser.runtime.onConnect.addListener((port) => { } }); - // ───────────────────────────────────────────────────────────────────────────── -// (Code commenté concernant l'activation/désactivation de l'analyse) +// Surlignage // ───────────────────────────────────────────────────────────────────────────── -// async function initializeExtensionState() { ... } -// initializeExtensionState(); +/** + * 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}`); + + try { + // S'assurer que le script est injecté + await browser.scripting.executeScript({ + target: { tabId: tabId }, + files: ["utils/highlighting.js"] + }); + // Envoyer le message d'activation + const response = await browser.tabs.sendMessage(tabId, { + command: command, + lexiconId: lexiconId + }); + + log("Réponse du content script:", response); + return response; + } catch (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 0c74f175906ad8750c62d8d3d8bcfb04c1343a10..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 === -const WHITE_BOX_ID = "whiteBox"; +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ 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,38 +22,57 @@ 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; } } /** - * Crée le menu contextuel personnalisé (whiteBox) s'il n'existe pas déjà . + * 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 injectWhiteBox() { - let whiteBox = document.getElementById(WHITE_BOX_ID); - if (!whiteBox) { - whiteBox = document.createElement("div"); - whiteBox.id = WHITE_BOX_ID; - whiteBox.style.position = "absolute"; - whiteBox.style.zIndex = "9999"; - whiteBox.style.backgroundColor = "#fff"; - whiteBox.style.border = "1px solid #ccc"; - whiteBox.style.padding = "5px"; - whiteBox.style.borderRadius = "4px"; - whiteBox.style.boxShadow = "0px 2px 10px rgba(0,0,0,0.2)"; +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; + customContextMenu.style.position = "absolute"; + customContextMenu.style.zIndex = "9999"; + customContextMenu.style.backgroundColor = "#fff"; + customContextMenu.style.border = "1px solid #ccc"; + customContextMenu.style.padding = "5px"; + customContextMenu.style.borderRadius = "4px"; + customContextMenu.style.boxShadow = "0px 2px 10px rgba(0,0,0,0.2)"; 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 - whiteBox.innerHTML = ` + customContextMenu.innerHTML = ` <p id="selectedWord" style="margin: 0; padding: 0;">Mot sélectionné : Aucun</p> <hr style="border: 0; height: 1px; background-color: #323046; margin: 8px 0;"> <div style="display: flex; flex-wrap: wrap; justify-content: center;"> @@ -62,26 +90,32 @@ function injectWhiteBox() { </div> </div> `; - document.body.appendChild(whiteBox); - setupWhiteBoxActions(); + + // Ajoute le menu contextuel au corps du document + document.body.appendChild(customContextMenu); + + // Configure les actions des boutons du menu contextuel + setupCustomContextMenuActions(); } - whiteBox.addEventListener("mouseup", (e) => { + customContextMenu.addEventListener("mouseup", (e) => { e.stopPropagation(); }); - return whiteBox; + + return customContextMenu; } /** - * Renvoie le whiteBox s'il existe, ou le crée. + * Renvoie le customContextMenu s'il existe, ou le crée. + * @returns {HTMLElement} - Le menu contextuel. */ -function getOrCreateWhiteBox() { - return document.getElementById(WHITE_BOX_ID) || injectWhiteBox(); +function getOrCreateCustomContextMenu() { + return document.getElementById(CUSTOM_CONTEXT_MENU) || injectCustomContextMenu(); } /** * Configure les actions des boutons du menu contextuel. */ -function setupWhiteBoxActions() { +function setupCustomContextMenuActions() { const addLexiconBtn = document.getElementById("addLexiconButton"); const getDefinitionBtn = document.getElementById("getDefinitionButton"); const loginBtn = document.getElementById("loginButton"); @@ -95,7 +129,7 @@ function setupWhiteBoxActions() { 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" }); @@ -126,7 +160,7 @@ function setupWhiteBoxActions() { * Met à jour la visibilité des boutons du menu selon l'authentification. */ function updateMenuVisibility() { - getOrCreateWhiteBox(); + getOrCreateCustomContextMenu(); const addLexiconBtn = document.getElementById("addLexiconButton"); const getDefinitionBtn = document.getElementById("getDefinitionButton"); const loginBtn = document.getElementById("loginButton"); @@ -141,31 +175,25 @@ function updateMenuVisibility() { getDefinitionBtn.style.display = "inline-block"; loginBtn.style.display = "none"; } else { - hideWhiteBox(); + hideCustomContextMenu(); addLexiconBtn.style.display = "none"; getDefinitionBtn.style.display = "inline-block"; loginBtn.style.display = "inline-block"; } } -/** - * 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 showWhiteBox(event, selectedText) { +async function showCustomContextMenu(event, selectedText) { const { extensionActive } = await browser.storage.local.get("extensionActive") || { extensionActive: false }; if (!extensionActive || !authToken) { - hideWhiteBox(); + hideCustomContextMenu(); return; } - const whiteBox = getOrCreateWhiteBox(); + const customContextMenu = getOrCreateCustomContextMenu(); const selectedWordElement = document.getElementById("selectedWord"); selectedWordElement.textContent = selectedText; @@ -176,87 +204,63 @@ async function showWhiteBox(event, selectedText) { const top = rect.bottom + window.scrollY; const left = rect.right + window.scrollX; - whiteBox.style.left = left + "px"; - whiteBox.style.top = top + "px"; - whiteBox.style.display = "block"; + customContextMenu.style.left = left + "px"; + customContextMenu.style.top = top + "px"; + customContextMenu.style.display = "block"; log("Affichage du menu contextuel avec le mot :", selectedText); updateMenuVisibility(); } -function hideWhiteBox() { - const whiteBox = document.getElementById(WHITE_BOX_ID); - if (whiteBox) { - whiteBox.style.display = "none"; +/** + * Masque le menu contextuel personnalisé. + */ +function hideCustomContextMenu() { + const customContextMenu = document.getElementById(CUSTOM_CONTEXT_MENU); + if (customContextMenu) { + customContextMenu.style.display = "none"; } } -// Écoute globale pour la sélection de texte -document.addEventListener("mouseup", (event) => { - if (event.target.closest("#whiteBox")) return; - const selectedText = window.getSelection().toString().trim(); - if (selectedText) { - log("Texte sélectionné :", selectedText); - getOrCreateWhiteBox(); - showWhiteBox(event, selectedText); - browser.runtime.sendMessage({ - action: "mot_selectionne", - selectedText, - }); - } else { - hideWhiteBox(); - } -}); - -// É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(() => { - getOrCreateWhiteBox(); - 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 whiteBox = document.getElementById(WHITE_BOX_ID); - 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(); + } +}); - if (whiteBox && !whiteBox.contains(event.target)) { - hideWhiteBox(); +// É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); - showWhiteBox(event, 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/css/custom_context_menu.css b/src/css/custom_context_menu.css new file mode 100644 index 0000000000000000000000000000000000000000..66c89329490d1300b780f57fdec0fb096ddd99d9 --- /dev/null +++ b/src/css/custom_context_menu.css @@ -0,0 +1,155 @@ +/* Import de la police Luciole */ +@font-face { + font-family: 'Luciole'; + src: url('../fonts/Luciole-Regular/Luciole-Regular.woff2') format('woff2'), + url('../fonts/Luciole-Regular/Luciole-Regular.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +/* Conteneur principal du menu contextuel */ +#customContextMenu { + position: absolute; + display: none; + min-width: 50px; + max-width: 300px; + background-color: white; + color: #323046; + border: 2px solid #323046; + border-radius: 10px; + padding: 10px; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); + font-family: Luciole; + z-index: 10000; +} + +/* Mot sélectionné */ +#customContextMenu #selectedWord { + margin: 0; + margin-bottom: 8px; + font-size: 14px; + line-height: 1.3; + color: #323046; + font-weight: bold; + text-align: center; +} + +/* Icônes */ +#customContextMenu .icon-container { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + margin: 0; + padding: 0; +} +#customContextMenu .icon-container:hover { + background-color: rgba(255, 255, 255, 0.1); + border-radius: 6px; +} +#customContextMenu .icon { + width: 40px; + height: 40px; + transition: transform 0.2s ease; + margin : 0 auto; + display: block; +} +#customContextMenu .icon:hover { + transform: scale(1.15); +} + +/* Messages d'information (tooltips) */ +#customContextMenu .tooltip { + visibility: hidden; + background-color: #333; + color: #fff; + text-align: center; + padding: 6px; + border-radius: 5px; + position: absolute; + bottom: -34px; + left: 50%; + transform: translateX(-50%); + white-space: nowrap; + font-size: 12px; + opacity: 0; + transition: opacity 0.2s ease, visibility 0.2s ease; + z-index: 1000; +} +#customContextMenu .icon-container:hover .tooltip { + visibility: visible; + opacity: 1; +} + +/* Style du sélectionneur de lexiques */ +#lexiconPicker { + position: absolute; + z-index: 10000; + background-color: rgba(255, 255, 255, 0.98); + border: 1px solid #ddd; + padding: 4px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width: auto; + font-family: Luciole; + font-size: 10px; + display: flex; + flex-direction: row !important; + flex-wrap: wrap !important; + align-items: center; + justify-content: center; + gap: 2px; +} +#lexiconPicker p { + font-size: 12px; + font-weight: bold; + text-align: center; +} + +/* Style pour les icônes de lexique */ +#lexiconPicker .lexicon-option { + cursor: pointer; + display: inline-flex !important; + flex-direction: row !important; + align-items: center; + justify-content: center; + border: 2px solid transparent; + border-radius: 50%; + width: 40px; + height: 40px; + transition: border 0.2s ease; + flex: 0 0 auto; +} +/* Effet au survol pour les icônes */ +#lexiconPicker .lexicon-option:hover { + border: 2px solid #6e76c7; +} +/* Indiquer qu'une icône est sélectionnée */ +#lexiconPicker .lexicon-option.selected { + border: 2px solid #323046; +} +#lexiconPicker .color-circle { + width: 28px; + height: 28px; + border-radius: 50%; + display: inline-block; + border: 1px solid black; +} + +/* Style pour le bouton de confirmation */ +#lexiconPicker button.confirmButton { + font-style: italic; + font-size: 10px; + padding: 6px 10px; + cursor: pointer; + border: none; + align-items: center; + text-align: center; + border-radius: 4px; + background-color: #323046; + color: white; + flex-basis: 100%; + margin-top: 8px; +} diff --git a/src/css/plugin.css b/src/css/plugin.css new file mode 100644 index 0000000000000000000000000000000000000000..6d8a5bd7eabc3e9efa15602cf54db1a5e29de149 --- /dev/null +++ b/src/css/plugin.css @@ -0,0 +1,372 @@ +/* Import de la police Luciole */ +@font-face { + font-family: 'Luciole'; + src: url('../fonts/Luciole-Regular/Luciole-Regular.woff2') format('woff2'), + url('../fonts/Luciole-Regular/Luciole-Regular.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +/* Styles généraux */ +body { + font-family: Luciole; + font-size: medium; + margin: 0; + padding: 8px; + background-color: #525877; + color: #323046; + border-radius: 10px; + width: 200px; +} +.hidden { + display: none; +} +button { + font-family: Luciole; + width: 100%; + padding: 12px; + margin-bottom: 8px; + font-size: 14px; + font-weight: bold; + border: none; + cursor: pointer; + border-radius: 6px; + transition: background 0.3s, transform 0.2s; + background-color: #a08e9f; + color: white; +} +button:hover { + background-color: #dddedd; + color: #8d5c70; +} + +/* En-tête de l'extension */ +#extension-name { + font-family: Luciole; + text-align: center; + font-size: 23px; + font-weight: bold; + padding: 5px; + color: #8d5c70; + -webkit-text-stroke-width: 1px; + -webkit-text-stroke-color: white; +} + +/* Bouton de connexion */ +#auth-button { + width: auto; + display: inline-flex; + padding: 6px 12px; + font-size: 16px; + font-family: Luciole; + background: none; + border: none; + color: white; + align-items: center; + gap: 6px; + border-radius: 20px; + cursor: pointer; + transition: background 0.3s; + text-align: center; +} +#auth-button:hover { + background: rgba(255,255,255,0.2); +} +#auth-button svg { + width: 18px; + height: 18px; + fill: white; + transition: transform 0.3s ease-in-out; +} +#auth-button:hover svg { + transform: scale(1.1); +} +#auth-section { + display: flex; + justify-content: center; + margin-bottom: 10px; +} + +/* Options du menu de l'extension */ +.option-container { + background: #444; + padding: 8px; + border-radius: 10px; + box-shadow: 0 2px 6px rgba(0,0,0,0.2); +} +.option-row { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: nowrap; + padding: 12px; + border-radius: 6px; + background-color: #444; + color: white; + transition: transform 0.2s, box-shadow 0.2s; +} +.option-row:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} +.option-row label { + font-weight: lighter; + flex: 1; + margin: 0; + font-size: 13px; + align-items: center; +} +.option-row input[type="checkbox"], +.option-row input[type="number"] { + appearance: none; + width: 20px; + height: 20px; + border: 2px solid #8d5c70; + border-radius: 50%; + background-color: #fff; + cursor: pointer; + transition: background-color 0.3s, border-color 0.3s; +} +.option-row input[type="checkbox"]:checked { + background-color: #8d5c70; + border-color: #8d5c70; +} + +/* Bouton pour ouvrir les statistiques */ +#open-stats { + padding: 6px; + font-weight: lighter; + width: auto; + display: block; + margin: 0 auto; + margin-bottom: 10px; + background-color: #525877; + color: white; + border: 2px solid #8d5c70; + border-radius: 8px; +} + +/* Bloc d'options d'ajout automatique */ +.option-row.auto-add-row { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; + margin-bottom: 0; + background: transparent; + border-bottom: 1px solid #555; +} +.option-row.auto-add-row span { + font-size: 14px; + font-weight: lighter; +} +.option-row.stopwords { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 13px; + font-weight: lighter; +} + +/* Bouton pour afficher/masquer le bloc d'options d'ajout automatique et la sélection d'options*/ +.toggle-switch { + position: absolute; + display: inline-block; + width: 30px; + height: 16px; + right: 0; + left: auto; +} +.toggle-switch .slider:before { + position: absolute; + content: ""; + width: 12px; + height: 12px; + left: 2px; + bottom: 2px; + background-color: white; + transition: 0.4s; + border-radius: 50%; +} +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: 0.4s; + border-radius: 24px; +} +input:checked + .slider { + background-color: #8d5c70; +} +input:checked + .slider:before { + transform: translateX(14px); +} + +/* Conteneur pour le seuil d'ajout automatique */ +.threshold-container input[type="number"] { + width: 45px; + height: 45px; + line-height: 50px; + text-align: center; + border-radius: 50%; + border: 2px solid #8d5c70; + font-size: 13px; + box-sizing: border-box; + background: #fff; + color: #333; +} + +/* Sélection des langues */ +.language-selection { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.lang-option { + padding: 4px 10px; + border: 2px solid #8d5c70; + border-radius: 16px; + background: rgb(152, 152, 152); + color: #8d5c70; + cursor: pointer; + font-size: 0.85rem; + transition: background 0.3s, color 0.3s, transform 0.2s; +} +.lang-option:hover { + background: #8d5c70; + color: white; + transform: scale(1.05); +} +.lang-option.selected { + background: #8d5c70; + color: white; + border-color: #8d5c70; +} + +/* Bouton pour enregistrer les options */ +#save-options { + border: none; + background: #8d5c70; + border-radius: 6px; + color: white; + padding: 8px 12px; + font-size: 14px; + cursor: pointer; + transition: background 0.3s, transform 0.2s; +} +#save-options:hover { + background: #ccc; + color: #8d5c70; + transform: translateY(-2px); +} + +/* Messages d'information (tooltips) */ +.tooltip-container { + position: relative; + display: inline-block; + pointer-events: auto !important; +} +.tooltip { + all: unset; + display: block; + box-sizing: border-box; + position: absolute; + left: 50%; + transform: translateX(-50%); + color: #fff !important; + font-size: 12px !important; + font-weight: lighter !important; + padding: 6px 10px; + border-radius: 5px; + white-space: normal; + overflow-wrap: break-word; + width: 200px; + text-align: center; + visibility: hidden; + transition: visibility 0.3s ease-in-out, transform 0.3s ease-in-out; + pointer-events: none; + z-index: 1000; + line-height: normal; +} +.tooltip-container .tooltip { + bottom: 120%; + transform: translateX(-50%); + background-color: rgba(0,0,0,0.9) !important; + visibility: hidden; + pointer-events: auto !important; +} +.tooltip-container:hover .tooltip { + visibility: visible !important; + transform: translateX(-50%) translateY(-5px); + pointer-events: auto !important; +} +#auth-button .tooltip { + top: 120%; + bottom: auto; +} +#auth-button.tooltip-container:hover .tooltip { + visibility: visible !important; + transform: translateX(-50%) translateY(5px); +} +/* Permettre l'interaction avec les tooltips même sur les boutons désactivés */ +button:disabled .tooltip { + pointer-events: auto !important; +} + +.tooltip-langues-suivies { + left: 50% !important; + transform: translateX(-28%) !important; +} + +/* Notifications et erreurs */ +#extension-notification { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.85); + color: white; + padding: 15px; + border-radius: 8px; + text-align: center; + width: 80%; + max-width: 250px; + z-index: 1000; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); + transition: visibility 0.3s ease; +} +#extension-notification.hidden { + visibility: hidden; +} + +#close-notification { + margin-top: 10px; + padding: 5px 10px; + background-color: #8d5c70; + color: white; + border: none; + cursor: pointer; + border-radius: 5px; + font-weight: bold; +} +#close-notification:hover { + background-color: #dddedd; + color: #8d5c70; +} +#error-message { + font-size: 13px; + font-style: italic; + text-align: center; + color: white; +} diff --git a/src/css/sidebar.css b/src/css/sidebar.css new file mode 100644 index 0000000000000000000000000000000000000000..76e6dc47dca1b3ee394a2140d59132ae7a59df7e --- /dev/null +++ b/src/css/sidebar.css @@ -0,0 +1,399 @@ +/* Import de la police Luciole */ +@font-face { + font-family: 'Luciole'; + src: url('../fonts/Luciole-Regular/Luciole-Regular.woff2') format('woff2'), + url('../fonts/Luciole-Regular/Luciole-Regular.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +/* Style global */ +body { + font-family: Luciole; + font-size: medium; + margin: 0; + padding: 10px; + background-color: #525877; + color: #323046; +} + +/* Conteneurs principaux */ +#menu, #etat, #definitionContainer { + padding: 10px; + margin-bottom: 10px; + border-radius: 10px; + background-color: #a08e9f; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + overflow: visible; +} + +/* En-têtes et contenu des blocs */ +.block-header { + position: relative; + text-align: center; + margin-top: 2px; +} +.block-header h3 { + display: inline-block; + margin-top: 5px; + margin-bottom: 0; +} +.block-content { + padding-top: 2px; +} +.hidden { + display: none; +} + +/* Boutons */ +button { + font-family: Luciole; + width: 100%; + margin-top: 5px; + padding: 10px; + border: none; + background-color: #8d5c70; + color: #fbfcfc; + font-weight: bold; + cursor: pointer; + text-align: center; + border-radius: 5px; +} +button:hover { + background-color: #dddedd; + color: #8d5c70; +} + +/* Bouton de connexion */ +#auth-button { + width: auto; + display: inline-flex; + padding: 6px 12px; + font-size: 16px; + font-family: Luciole; + background: none; + border: none; + color: white; + align-items: center; + gap: 6px; + border-radius: 20px; + cursor: pointer; + transition: background 0.3s; +} +#auth-button:hover { + background: rgba(255,255,255,0.2); +} +#auth-button svg { + width: 18px; + height: 18px; + fill: white; + transition: transform 0.3s ease-in-out; +} +#auth-button:hover svg { + transform: scale(1.1); +} +#auth-section { + display: flex; + justify-content: flex-end; + margin-bottom: 10px; +} + +/* Boutons de bascule (toggle) */ +.toggle-btn { + position: absolute; + right: 8px; + background: none; + margin: 0; + border: none; + color: #fff; + font-size: 15px; + padding: 3px 5px; + cursor: pointer; + width: auto; + display: inline-block; +} + +/* Mot sélectionné */ +#motSelectionne { + font-style: italic; + text-align: center; + margin-top: 5px; +} + +/* Style des lexiques */ +.lexique-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px; + margin-bottom: 5px; + border-radius: 5px; + background-color: #dcdde1; + position: relative; +} +.lexique-item:hover { + background-color: #c4c7ce; +} +.lexique-label { + font-weight: bold; + color: #323046; + flex-grow: 1; + font-size: 12px; + text-align: center; +} + +/* Icône du lexique */ +.lexique-icon { + width: 25px; + height: 25px; + border-radius: 50%; + background-color: #ccc; + margin-right: 10px; + flex-shrink: 0; +} + +/* Messages d'information (tooltips) */ +.tooltip { + all: unset; + display: block; + box-sizing: border-box; + position: absolute; + bottom: 120%; + left: 50%; + transform: translateX(-50%); + background-color: rgba(0,0,0,0.75); + color: #fff; + font-size: 12px !important; + font-weight: lighter !important; + padding: 6px 10px; + border-radius: 5px; + white-space: normal; + overflow-wrap: break-word; + width: 180px; + text-align: center; + opacity: 0; + transition: opacity 0.3s ease-in-out, transform 0.2s ease-in-out; + pointer-events: none; + z-index: 10; + line-height: normal; +} +.tooltip-container { + position: relative; + display: inline-block; + cursor: pointer; + overflow: visible; +} +.tooltip-container:hover .tooltip { + opacity: 1; + transform: translateX(-50%) translateY(-5px); +} +.tooltip-container.left .tooltip { + left: 0; + transform: translateX(0) translateY(-5px); +} +.tooltip-container.right .tooltip { + right: 0; + left: auto; + transform: translateX(0) translateY(-5px); +} +/* Tooltip pour le bouton de surlignage */ +button.lexique-highlight-toggle .tooltip { + all: unset; + display: block; + box-sizing: border-box; + position: absolute; + bottom: 120%; + left: 50%; + transform: translateX(-50%) translateY(-5px); + background-color: rgba(0, 0, 0, 0.75); + color: #fff; + font-size: 14px; + font-weight: lighter; + padding: 6px 10px; + border-radius: 5px; + white-space: normal; + overflow-wrap: break-word; + width: 180px; + text-align: center; + opacity: 0; + transition: opacity 0.3s ease-in-out, transform 0.2s ease-in-out; + pointer-events: none; + z-index: 10; + line-height: normal; +} +button.lexique-highlight-toggle:hover .tooltip { + opacity: 1; + transform: translateX(-50%) translateY(-5px); +} + +/* Section Lexique */ +.lexicon-section { + margin-bottom: 10px; +} +.lexicon-header { + font-weight: bold; + cursor: pointer; + padding: 5px; + background-color: #8d5c70; + border-radius: 5px; + text-align: center; +} +.lexicon-header:hover { + background-color: #dddedd; + color: #8d5c70; +} +.lexicon-content { + margin-top: 5px; +} +.lexicon-option { + margin-right: -10px; +} +#mesLexiquesList { + display: inline; + padding: 0; + align-items: center; +} +#mesLexiquesContainer h4 { + margin-bottom: 5px; +} + +/* Cases à cocher pour les lexiques */ +.lexique-checkbox { + appearance: none; + width: 20px; + height: 20px; + border: 2px solid #8d5c70; + border-radius: 5px; + background-color: #fff; + transition: background 0.3s ease, border-color 0.3s ease; + cursor: pointer; + position: relative; +} +.lexique-checkbox:hover { + border-color: #6a3e50; +} +.lexique-checkbox:checked { + background-color: #8d5c70; + border-color: #8d5c70; +} +.lexique-checkbox:checked::after { + content: '✔'; + font-size: 16px; + color: white; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +/* Surlignage */ +.lexique-highlight-toggle { + background: none; + border: none; + cursor: pointer; + padding: 2px; + transition: transform 0.2s ease-in-out; + width: 15%; + position: relative; +} +.feutre-icon { + width: 20px; + height: 20px; + filter: brightness(0) saturate(100%) invert(40%) sepia(0%) saturate(0%) hue-rotate(0deg); + transition: filter 0.3s ease-in-out; +} +/* Icone active */ +.lexique-highlight-toggle.active .feutre-icon, +.lexique-highlight-toggle[data-active="true"] .feutre-icon { + filter: brightness(0) saturate(100%) invert(83%) sepia(89%) saturate(588%) hue-rotate(360deg); +} +.lexicon-highlight { + position: relative; + display: inline-block; + padding-bottom: 4px; + border-bottom: 1px dashed #666; + transition: background-color 0.3s; + background-color: rgba(255, 255, 0, 0.15); + +} +/* Bandes de couleurs pour le surlignage */ +.color-bands { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 3px; + display: flex; +} +.color-bands div { + flex: 1; + height: 100%; +} +#highlighting-options p { + margin: 5px 0; + font-size: small; + color: #333; +} + +/* Section Définitions */ +#definitionContainer { + background-color: #444; + padding: 10px; + border-radius: 10px; + color: white; +} +#definitionsList { + list-style: none; + padding: 0; +} +#definitionsList li { + margin-bottom: 10px; +} +.definition-source { + font-weight: bold; + color: #ffa500; +} +#noDefinitionsContainer { + display: block !important; + color: red !important; + font-weight: bold; +} + +/* Modal de définition */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0,0,0,0.5); + display: none; + align-items: center; + justify-content: center; + z-index: 9999; +} +.modal-content { + background: white; + color: #8d5c70; + padding: 1rem; + max-width: 600px; + max-height: 80vh; + overflow-y: auto; + border-radius: 8px; +} +.close-button { + float: right; + cursor: pointer; + font-weight: bold; + color: #666; +} +.close-button:hover { + color: #000; +} + +/* Message si utilisateur déconnecté/analyse désactivée */ +#messageContainer { + display: none; + text-align: center; + color: #323046; +} \ No newline at end of file diff --git a/src/plugin/plugin.html b/src/plugin/plugin.html new file mode 100644 index 0000000000000000000000000000000000000000..ca17e78d592e801a20fe1145af7ac348e654abf3 --- /dev/null +++ b/src/plugin/plugin.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html lang="fr"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Extension BaLex</title> + <script src="../utils/logger.js"></script> + <link rel="stylesheet" href="../css/plugin.css"> +</head> +<body> + <div id="extension-name">Extension BaLex</div> + + <!-- Section de connexion --> + <div id="auth-section"> + <button id="auth-button"> + <span id="auth-icon"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <path d="M10 2a1 1 0 0 1 1 1v2h2V3a1 1 0 0 1 2 0v2h2a2 2 0 0 1 2 2v3h-2V7h-2v3a1 1 0 1 1-2 0V7h-2v3a1 1 0 1 1-2 0V7H6v3H4V7a2 2 0 0 1 2-2h2V3a1 1 0 0 1 1-1Z"/> + </svg> + </span> + <span id="auth-text">Se connecter</span> + </button> + </div> + + <!-- Section des options de l'extension --> + <button id="toggleExtensionBtn">Activer/Désactiver</button> + <button id="toggleStatsBtn">Statistiques</button> + <button id="open-stats">Afficher les statistiques</button> + <!-- Indicateur de chargement de Pyodide --> + <div id="pyodide-loading"></div> + + <!-- Section des options d'ajout automatique --> + <div id="stats-options" class="option-container"> + <div id="auto-add-container" class="option-row auto-add-row"> + <span>Ajout automatique</span> + <label class="toggle-switch"> + <input type="checkbox" id="auto-add"> + <span class="slider"></span> + </label> + </div> + <div id="auto-add-options" class="hidden"> + <div class="option-row stopwords"> + <span>Inclure mots outils</span> + <label class="toggle-switch"> + <input type="checkbox" id="include-stopwords" /> + <span class="slider"></span> + </label> + </div> + <div class="option-row threshold-container"> + <label for="threshold">Seuil d'ajout d'un mot</label> + <input type="number" id="threshold" value="10" min="1" /> + </div> + <div class="option-row"> + <label>Langues suivies</label> + <div id="language-selection" class="language-selection"> + <p id="loading-languages" style="color: gray;">Chargement...</p> + </div> + </div> + <!-- Message d'erreur si aucune langue sélectionnée --> + <div id="error-message" class="hidden"> + <p>Veuillez sélectionner une ou plusieurs langue(s).</p> + </div> + <button id="save-options" class="hidden">Valider</button> + </div> + </div> + + <!-- Notification de l'extension --> + <div id="extension-notification" class="hidden"> + <p id="notification-text"></p> + <button id="close-notification">OK</button> + </div> + + <script src="../utils/api.js"></script> + <script src="plugin.js"></script> +</body> +</html> diff --git a/src/plugin/plugin.js b/src/plugin/plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..57f873074c478b902f3d8ebe9c61e6a6aa86a046 --- /dev/null +++ b/src/plugin/plugin.js @@ -0,0 +1,577 @@ +log("plugin chargé avec succès !"); +// fetchExtensionState() : fonction qui se charge de récupérer les valeurs du local storage +// updateUI(): utilise les états récupérés par fetchExtensionState pour appeler d'autres fonctions +// ex. actualisation du bouton de connexion, bouton activer l'extension, les stats... +// setupEventListeners() : fonction qui regroupe les écouteurs d'évènement +// handleEvent() : une description de chaque évènement avec les changements de valeur du local storage + + +// ==================================================================================== +// Fonctions utilitaires +// ==================================================================================== + +//Obtenir le token +async function getAccessToken() { + const { accessToken } = await browser.storage.local.get("accessToken"); + return accessToken; +} + +// ========= +// Fonction d'actualisation générale de la popup : 1. Récupération des valeurs 2. Mà j UI +async function updateExtension() { + states = await fetchExtensionState(); //Récupérer les valeurs + updateUI(states); // Selon les valeurs, mettre à jour l'UI +} + +// ========= +// 1. Récupérer les valeurs du local storage +async function fetchExtensionState() { + const accessToken = await getAccessToken(); + const storedValues = await browser.storage.local.get([ + "extensionActive", + "isTrackingActive", + "autoAdd", + "threshold", + "pyodideSimplemmaReady", + "includeStopwords" + ]); + return { + isLoggedIn: !!accessToken, + extensionActive: storedValues.extensionActive ?? false, + isTrackingActive: storedValues.isTrackingActive ?? false, + autoAdd: storedValues.autoAdd ?? false, + threshold: storedValues.threshold ?? 10, + pyodideSimplemmaReady: storedValues.pyodideSimplemmaReady ?? false, + includeStopwords: storedValues.includeStopwords ?? false + }; +} + +// ========= +// 2.Fonction de mise à jour de l'UI +async function updateUI(states) { + await updateConnectionButton(states.isLoggedIn); //Actualisation du bouton de connexion + await updateToggleExtensionButton(states.isLoggedIn, states.extensionActive, states.autoAdd, states.isTrackingActive, states.pyodideSimplemmaReady, states.includeStopwords); + // éventuellement ajouter des fonctions issues du toggleExtensionButton pour la lisibilité : + //await updateStatsButtons(states.isLoggedIn, states.extensionActive, states.isTrackingActive); + // await updateAutoAddOptions(states.isLoggedIn, states.extensionActive, states.autoAdd); + // await updatePyodideStatus(states.isLoggedIn, states.extensionActive, states.isTrackingActive, state.pyodideSimplemmaReady); + await updateLanguageSelection(); + await updateStopwordsOption(states.includeStopwords); + console.log("✅ Interface mise à jour :", states); +} + + + +// ==================================================================================== +// Fonction contenant les écouteurs d'évènements et gestion des valeurs du local storage +// ==================================================================================== +function setupEventListeners() { + // Bouton Connexion / Déconnexion + document.getElementById("auth-button")?.addEventListener("click", handleAuthToggle); + + // Bouton activer l'extension + document.getElementById("toggleExtensionBtn")?.addEventListener("click", handleToggleExtension); + + // Bouton de gestion des statistiques + document.getElementById("toggleStatsBtn")?.addEventListener("click", handleStatsToggle); + + // Gestion de l'ajout automatique + document.getElementById("auto-add")?.addEventListener("change", handleAutoAddToggle); + + //Activation/désactivation des stopwords + document.getElementById("include-stopwords")?.addEventListener("change", handleStopwordsToggle); + + // Sauvegarde des options + document.getElementById("save-options")?.addEventListener("click", handleSaveOptions); + + // Ouverture de la page des statistiques + // TODO : ajouter l'évènement + document.getElementById("open-stats")?.addEventListener("click", () => { + window.open("stats.html", "_blank"); + }); +} + +// =========== +//Description de chaque évènement + +//Connexion / Déconnexion +async function handleAuthToggle() { + const accessToken = await getAccessToken(); + if (!accessToken) { + console.log("🔓 Connexion demandée..."); + await browser.runtime.sendMessage({ action: "toggleAuth" }); + } else { + console.log("🔒 Déconnexion demandée..."); + await browser.storage.local.set({ + accessToken: null, + autoAdd: false, + includeStopwords: false, + isTrackingActive: false + }); + browser.runtime.sendMessage({ + command: "update-preferences", + autoAdd: false, + includeStopwords: false, + isTrackingActive: false + }); + console.log("✅ Paramètres réinitialisés après déconnexion."); + } + await updateExtension(); +} + +//Statistiques +async function handleStatsToggle() { + const accessToken = await getAccessToken(); + if (!accessToken) return; + // Récupérer l'état actuel des statistiques + const { isTrackingActive } = await browser.storage.local.get({ isTrackingActive: false }); + const newState = !isTrackingActive; + // Mise à jour uniquement de `isTrackingActive` + await browser.storage.local.set({ isTrackingActive: newState }); + console.log("📊 Nouvel état des statistiques :", newState); + // Envoi du message de mise à jour + browser.runtime.sendMessage({ command: "toggle-stats", isActive: newState }); + // Exécution de Pyodide si nécessaire + if (newState) { + browser.runtime.sendMessage({ command: "pyodide-simplemma" }); + } + + if (isUpdatingLexicons) return; + isUpdatingLexicons = true; + await updateLexiconsDisplay(); + isUpdatingLexicons = false; + await updateExtension(); +} + +// Activer l'extension +async function handleToggleExtension() { + const accessToken = await getAccessToken(); + if (!accessToken) return; + + const { extensionActive, isTrackingActive } = await browser.storage.local.get({ extensionActive: false, isTrackingActive: false }); + const newState = !extensionActive; + + await browser.storage.local.set({ extensionActive: newState }); + + if (!newState) { + await browser.storage.local.set({ isTrackingActive: false }); + if (isTrackingActive) window.open("stats.html", "_blank"); + browser.runtime.sendMessage({ action: "closeSidebarBlocks" }); + } + + browser.runtime.sendMessage({ action: "toggleExtension", isActive: newState }); + await updateExtension(); +} + + +//Ajout automatique +function handleAutoAddToggle() { + const autoAddCheckbox = document.getElementById("auto-add"); + const autoAddOptions = document.getElementById("auto-add-options"); + const saveOptionsBtn = document.getElementById("save-options"); + + if (!autoAddCheckbox || !autoAddOptions || !saveOptionsBtn) return; + + const isAutoAddEnabled = autoAddCheckbox.checked; + + // Juste afficher ou cacher les options, mais ne pas sauvegarder dans le local storage + autoAddOptions.classList.toggle("hidden", !isAutoAddEnabled); + saveOptionsBtn.classList.toggle("hidden", !isAutoAddEnabled); + + // Si on décoche, désactiver immédiatement et forcer la sauvegarde + if (!isAutoAddEnabled) { + browser.storage.local.set({ autoAdd: false, includeStopwords: false }); + document.getElementById("include-stopwords").checked = false; + console.log("Ajout automatique désactivé → Stopwords désactivés immédiatement."); + } +} + + +// Gestion de l'activation/désactivation des stopwords +function handleStopwordsToggle() { + const stopwordsCheckbox = document.getElementById("include-stopwords"); + if (!stopwordsCheckbox) return; + + // Si décoché, forcer immédiatement la mise à jour du local storage + if (!stopwordsCheckbox.checked) { + browser.storage.local.set({ includeStopwords: false }); + console.log("Stopwords désactivés immédiatement."); + } +} + + + +// Sauvegarde des options utilisateur +async function handleSaveOptions() { + const autoAddCheckbox = document.getElementById("auto-add"); + const stopwordsCheckbox = document.getElementById("include-stopwords"); + const threshold = parseInt(document.getElementById("threshold").value, 10); + const selectedLanguages = Array.from(document.querySelectorAll("#language-selection .lang-option.selected")) + .map(option => option.dataset.value); + + const errorMessage = document.getElementById("error-message"); + + if (autoAddCheckbox.checked && selectedLanguages.length === 0) { + errorMessage?.classList.remove("hidden"); + return; + } + errorMessage?.classList.add("hidden"); + + // Seule la validation met à jour le stockage local + await browser.storage.local.set({ + autoAdd: autoAddCheckbox.checked, + includeStopwords: stopwordsCheckbox.checked, + threshold, + trackedLanguages: selectedLanguages + }); + + browser.runtime.sendMessage({ + command: "update-preferences", + autoAdd: autoAddCheckbox.checked, + includeStopwords: stopwordsCheckbox.checked + }); + + await updateExtension(); + console.log("✅ Options sauvegardées."); +} + + +//Ouverture de la page des statistiques + +// ==================================================================================== +// Fonction actualisation UI des boutons +// ==================================================================================== + +// Gestion de la connexion +async function updateConnectionButton() { + const accessToken = await getAccessToken(); + //Vérification du bouton de connexion + const button = document.getElementById("auth-button"); + if (!button) { + console.error("⌠Le bouton de connexion n'a pas été trouvé."); + return; + } + if (accessToken) { + button.textContent = "Se déconnecter"; + button.style.position = "relative"; + button.className = "tooltip-container"; + const tooltip = document.createElement("span"); + tooltip.className = "tooltip"; + tooltip.textContent = "En vous déconnectant, vous perdrez l'accès à vos lexiques personnels, ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation."; + button.appendChild(tooltip); + } else { + button.textContent = "Se connecter"; + button.style.position = "relative"; + button.className = "tooltip-container"; + const tooltip = document.createElement("span"); + tooltip.className = "tooltip"; + tooltip.textContent = "En vous connectant, vous pourrez accéder à vos lexiques personnels, ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation."; + button.appendChild(tooltip); + } +} + +// Gestion de la sélection des langues +async function updateLanguageSelection() { + const languageSelection = document.getElementById("language-selection"); + languageSelection.innerHTML = "<p id='loading-languages' style='color: gray;'>Chargement...</p>"; + + const storedData = await browser.storage.local.get("lexicons"); + const lexicons = storedData.lexicons || []; // Ne pas utiliser JSON.parse() + + if (!Array.isArray(lexicons) || lexicons.length === 0) { + log("Lexiques non trouvés, attente de la mise à jour..."); + languageSelection.innerHTML = "<p style='color: gray;'>En attente des lexiques...</p>"; + + // Écouteur pour détecter quand les lexiques sont stockés + const listener = (changes, area) => { + if (area === "local" && changes.lexicons) { + log("Lexiques détectés dans le stockage, mise à jour de la sélection !"); + browser.storage.onChanged.removeListener(listener); + updateLanguageSelection(); // Recharger l'affichage des langues + } + }; + browser.storage.onChanged.addListener(listener); + return; + } + + // Extraire les langues uniques + const userLanguages = [...new Set(lexicons.map(lex => lex.language))]; + + // Récupérer les langues suivies depuis le stockage + const { trackedLanguages } = (await browser.storage.local.get("trackedLanguages")) || { trackedLanguages: [] }; + + // Affichage des langues sous forme de boutons + languageSelection.innerHTML = ""; + userLanguages.forEach(lang => { + const langButton = document.createElement("div"); + langButton.classList.add("lang-option"); + langButton.textContent = lang.toUpperCase(); + langButton.dataset.value = lang; + + if (trackedLanguages && trackedLanguages.includes(lang)) { + langButton.classList.add("selected"); + } + + langButton.addEventListener("click", () => { + langButton.classList.toggle("selected"); + }); + + languageSelection.appendChild(langButton); + }); + + log("Sélection des langues mise à jour avec :", userLanguages); +} + +// ========= +// Gestion bouton d'activation de l'extension +async function updateToggleExtensionButton(isLoggedIn, extensionActive, autoAdd, isTrackingActive, pyodideSimplemmaReady, includeStopwords) { + const toggleExtensionBtn = document.getElementById("toggleExtensionBtn"); + + if (toggleExtensionBtn) { + toggleExtensionBtn.textContent = extensionActive ? "Désactiver l'analyse" : "Activer l'analyse"; + toggleExtensionBtn.style.pointerEvents = isLoggedIn ? "auto" : "none"; + toggleExtensionBtn.disabled = !isLoggedIn; + toggleExtensionBtn.style.position = "relative"; + toggleExtensionBtn.className = "tooltip-container"; + + const existingTooltipExt = toggleExtensionBtn.querySelector('.tooltip'); + if (existingTooltipExt) { + existingTooltipExt.remove(); + } + const tooltipExt = document.createElement("span"); + tooltipExt.className = "tooltip"; + tooltipExt.style.opacity = "1 !important"; + if (!isLoggedIn) { + tooltipExt.textContent = "Connectez-vous pour activer l'analyse"; + tooltipExt.style.display = "block"; + } else if (!extensionActive) { + tooltipExt.textContent = "Activer les fonctionnalités de l'extension : affichage des mots et des définitions de vos lexiques, ajout de mots, etc."; + tooltipExt.style.display = "block"; + } else { + tooltipExt.style.display = "none"; + } + toggleExtensionBtn.appendChild(tooltipExt); + } + + // Mise à jour des options de statistiques + const statsOptions = document.getElementById("stats-options"); + const toggleStatsBtn = document.getElementById("toggleStatsBtn"); + const openStats = document.getElementById("open-stats"); + if (statsOptions) { + statsOptions.style.display = (isLoggedIn && extensionActive) ? "block" : "none"; + } + + // Mise à jour du bouton des statistiques + if (toggleStatsBtn) { + const isEnabled = isLoggedIn && extensionActive; + toggleStatsBtn.textContent = isEnabled && isTrackingActive ? "Désactiver les statistiques" : "Activer les statistiques"; + toggleStatsBtn.style.pointerEvents = isEnabled ? "auto" : "none"; + toggleStatsBtn.disabled = !isEnabled; + toggleStatsBtn.style.position = "relative"; + toggleStatsBtn.className = "tooltip-container"; + const existingTooltipStats = toggleStatsBtn.querySelector('.tooltip'); + if (existingTooltipStats) { existingTooltipStats.remove(); } + const tooltipStats = document.createElement("span"); + tooltipStats.className = "tooltip"; + tooltipStats.style.opacity = "1 !important"; + if (!isLoggedIn) { + tooltipStats.textContent = "Connectez-vous pour accéder aux statistiques"; + tooltipStats.style.display = "block"; + } else if (!extensionActive) { + tooltipStats.textContent = "Veuillez activer l'analyse pour utiliser les statistiques"; + tooltipStats.style.display = "block"; + } else { + tooltipStats.style.display = "none"; + } + toggleStatsBtn.appendChild(tooltipStats); + } + if (openStats) { + openStats.style.display = (isLoggedIn && extensionActive && isTrackingActive) ? "block" : "none"; + } + + // Mise à jour des options d'ajout automatique + const autoAddContainer = document.getElementById("auto-add")?.parentElement; + const autoAddCheckbox = document.getElementById("auto-add"); + const autoAddOptions = document.getElementById("auto-add-options"); + const saveOptionsBtn = document.getElementById("save-options"); + + if (autoAddContainer) { + autoAddContainer.style.display = (isLoggedIn && extensionActive) ? "block" : "none"; + } + if (autoAddCheckbox && isLoggedIn) { + autoAddCheckbox.checked = autoAdd; + } + if (autoAddOptions) { + autoAddOptions.classList.toggle("hidden", !autoAdd); + } + if (saveOptionsBtn) { + saveOptionsBtn.classList.toggle("hidden", !autoAdd); + } + + // Mise à jour du message de chargement Pyodide + const statusContainer = document.getElementById('pyodide-loading'); + if (statusContainer) { + if (!isLoggedIn) { + statusContainer.innerHTML = ""; + } else if (!pyodideSimplemmaReady && extensionActive && isTrackingActive) { + statusContainer.innerHTML = "<p style='color: black; text-align: center; font-size: 11px;'>Chargement de l'extension en cours, veuillez patienter...</p>"; + } else if (pyodideSimplemmaReady && extensionActive && isTrackingActive) { + statusContainer.innerHTML = "<p style='color: black; text-align: center; font-size: 11px;'>C'est prêt !</p>"; + setTimeout(() => { + statusContainer.innerHTML = ""; + }, 2000); + } else { + statusContainer.innerHTML = ""; + } + } + + // Mise à jour de la sélection des langues + await updateLanguageSelection(); + + log("Interface mise à jour complètement", { + isLoggedIn, + extensionActive, + isTrackingActive, + autoAdd, + pyodideSimplemmaReady, + includeStopwords + }); +} + +//Activer/désactiver les stoplists +async function updateStopwordsOption(includeStopwords) { + const stopwordsCheckbox = document.getElementById("include-stopwords"); + if (stopwordsCheckbox) { + stopwordsCheckbox.checked = includeStopwords; + } +} + + + +// ✅ Actualisation de l'UI au chargement de la page +document.addEventListener("DOMContentLoaded", async () => { + await updateExtension(); // Mise à jour de l'extension selon les valeurs du local storage + setupEventListeners(); // Configuration des écouteurs d'événements +}); + +// !! boucle infinie ??? +// // Actualisation de l'UI en cas de changement dans le stockage local : ça c'est dans background qu'il faut le mettre xd +// browser.storage.onChanged.addListener((changes, area) => { +// if (area === "local" && changes.accessToken) { +// updateExtension(); +// } +// }); + +// ========================== +// Gestion des messages et du stockage +// ========================== +browser.runtime.onMessage.addListener(async (message) => { + log("📩 Message reçu dans popup.js :", message); + if (message.action === "updateUI") { + await updateExtension(); + } else if (message.action === "notify") { + alert(message.message); + } +}); + +browser.storage.onChanged.addListener((changes, area) => { + if (area === "local") { + updateExtension(); + } +}); + +// ========================== +// Gestion des notifications +// ========================== +function showNotification(message) { + const notificationBox = document.getElementById("extension-notification"); + const notificationText = document.getElementById("notification-text"); + const closeButton = document.getElementById("close-notification"); + + if (notificationBox && notificationText && closeButton) { + notificationText.textContent = message; + notificationBox.classList.remove("hidden"); + closeButton.addEventListener("click", () => { + notificationBox.classList.add("hidden"); + }, { once: true }); + } else { + log("⌠Impossible d'afficher la notification : élément manquant."); + } +} + +function handleWorkerMessage(event) { + const data = event.data; + log("[Background] Message du WebWorker :", data); + + if (data.type === "process-text" && data.status === "error") { + browser.runtime.sendMessage({ + action: "notify", + message: data.message + }); + return; + }} + + + + + + + + + + + + + + + + + + +// // Bouton pour ouvrir la page des statistiques +// document.getElementById("open-stats")?.addEventListener("click", async () => { +// await displayStatsSummary();//résumé dans la console +// window.open("stats.html", "_blank"); +// }); + +// async function displayStatsSummary() { +// console.log("[Popup] Préparation du résumé des statistiques..."); + +// // Récupérer les données stockées +// const { lemmaFrequencies, trackedLanguages, wordsAdded } = await browser.storage.local.get([ +// "lemmaFrequencies", +// "trackedLanguages", +// "wordsAdded" +// ]); + +// // Initialisation du résumé +// let summary = { +// totalWords: 0, // Nombre total de mots analysés +// totalUniqueWords: 0, // Nombre total de mots uniques +// languages: {}, // Nombre de mots analysés par langue +// wordsAdded: wordsAdded || {} // Mots ajoutés classés par langue +// }; + +// if (lemmaFrequencies) { +// Object.entries(lemmaFrequencies).forEach(([lang, words]) => { +// const wordCount = Object.values(words).reduce((sum, count) => sum + count, 0); +// const uniqueWordCount = Object.keys(words).length; + +// // Vérifier si la langue doit être suivie +// if (!trackedLanguages || trackedLanguages.includes(lang)) { +// summary.languages[lang] = { +// totalWords: wordCount, +// uniqueWords: uniqueWordCount +// }; + +// summary.totalWords += wordCount; +// summary.totalUniqueWords += uniqueWordCount; +// } +// }); +// } + +// console.log("[Popup] Résumé des statistiques mis à jour:", summary); + +// // Résumé dans le local storage +// await browser.storage.local.set({ statsSummary: summary }); +// } diff --git a/src/sidebar/sidebar.html b/src/sidebar/sidebar.html index 99ef99e15b5663b39e7fb6895196e675dfe416e6..d11af4608dc03da992f87a6bbd0282d4dbfd6126 100644 --- a/src/sidebar/sidebar.html +++ b/src/sidebar/sidebar.html @@ -9,409 +9,8 @@ <script src="../utils/api.js" defer></script> <script src="../utils/definitions.js" defer></script> <script src="sidebar.js" defer></script> - <style> - @font-face { - font-family: 'Luciole'; - src: url('../fonts/Luciole-Regular/Luciole-Regular.woff2') format('woff2'), - url('../fonts/Luciole-Regular/Luciole-Regular.woff') format('woff'); - font-weight: normal; - font-style: normal; - } - /* Style global */ - body { - font-family: Luciole; - font-size: medium; - margin: 0; - padding: 10px; - background-color: #525877; - color: #323046; - } + <link rel="stylesheet" href="../css/sidebar.css"> - /* Conteneurs principaux */ - #menu, #etat, #definitionContainer { - padding: 10px; - margin-bottom: 10px; - border-radius: 10px; - background-color: #a08e9f; - box-shadow: 0 2px 5px rgba(0,0,0,0.2); - overflow: visible; - } - - /* En-têtes de blocs */ - .block-header { - position: relative; - text-align: center; - margin-top: 2px; - } - .block-header h3 { - display: inline-block; - margin-top: 5px; - margin-bottom: 0; - } - - /* Bouton de connexion */ - #auth-button { - width: auto; - display: inline-flex; - padding: 6px 12px; - font-size: 16px; - font-family: Luciole; - background: none; - border: none; - color: white; - align-items: center; - gap: 6px; - border-radius: 20px; - cursor: pointer; - transition: background 0.3s; - } - #auth-button:hover { - background: rgba(255,255,255,0.2); - } - #auth-button svg { - width: 18px; - height: 18px; - fill: white; - transition: transform 0.3s ease-in-out; - } - #auth-button:hover svg { - transform: scale(1.1); - } - #auth-section { - display: flex; - justify-content: flex-end; - margin-bottom: 10px; - } - - /* Boutons de bascule (toggle) */ - .toggle-btn { - position: absolute; - right: 8px; - background: none; - margin: 0; - border: none; - color: #fff; - font-size: 15px; - padding: 3px 5px; - cursor: pointer; - width: auto; - display: inline-block; - } - - /* Contenu des blocs */ - .block-content { - padding-top: 2px; - } - .hidden { - display: none; - } - - /* Boutons standards */ - button { - font-family: Luciole; - width: 100%; - margin-top: 5px; - padding: 10px; - border: none; - background-color: #8d5c70; - color: #fbfcfc; - font-weight: bold; - cursor: pointer; - text-align: center; - border-radius: 5px; - } - button:hover { - background-color: #dddedd; - color: #8d5c70; - } - - /* Mot sélectionné */ - #motSelectionne { - font-style: italic; - text-align: center; - margin-top: 5px; - } - - /* Style des lexiques */ - .lexique-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px; - margin-bottom: 5px; - border-radius: 5px; - background-color: #dcdde1; - position: relative; - } - .lexique-item:hover { - background-color: #c4c7ce; - } - .lexique-label { - font-weight: bold; - color: #323046; - flex-grow: 1; - font-size: 12px; - text-align: center; - } - - /* Icône du lexique */ - .lexique-icon { - width: 25px; - height: 25px; - border-radius: 50%; - background-color: #ccc; - margin-right: 10px; - flex-shrink: 0; - } - - /* Conteneur pour tooltip (pour checkbox et surlignage) */ - .tooltip-container { - position: relative; - display: inline-block; - cursor: pointer; - overflow: visible; - } - - /* Style pour tous les tooltips */ - .tooltip { - all: unset; - display: block; - box-sizing: border-box; - position: absolute; - bottom: 120%; - left: 50%; - transform: translateX(-50%); - background-color: rgba(0,0,0,0.75); - color: #fff; - font-size: 12px !important; - font-weight: lighter !important; - padding: 6px 10px; - border-radius: 5px; - white-space: normal; - overflow-wrap: break-word; - width: 180px; - text-align: center; - opacity: 0; - transition: opacity 0.3s ease-in-out, transform 0.2s ease-in-out; - pointer-events: none; - z-index: 10; - line-height: normal; - } - - .tooltip-container:hover .tooltip { - opacity: 1; - transform: translateX(-50%) translateY(-5px); - } - .tooltip-container.left .tooltip { - left: 0; - transform: translateX(0) translateY(-5px); - } - .tooltip-container.right .tooltip { - right: 0; - left: auto; - transform: translateX(0) translateY(-5px); - } - - /* Cases à cocher personnalisées */ - .lexique-checkbox { - appearance: none; - width: 20px; - height: 20px; - border: 2px solid #8d5c70; - border-radius: 5px; - background-color: #fff; - transition: background 0.3s ease, border-color 0.3s ease; - cursor: pointer; - position: relative; - } - .lexique-checkbox:hover { - border-color: #6a3e50; - } - .lexique-checkbox:checked { - background-color: #8d5c70; - border-color: #8d5c70; - } - .lexique-checkbox:checked::after { - content: '✔'; - font-size: 16px; - color: white; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - - /* Bouton de surlignage */ - .lexique-highlight-toggle { - background: none; - border: none; - cursor: pointer; - padding: 2px; - transition: transform 0.2s ease-in-out; - width: 15%; - position: relative; - } - - .feutre-icon { - width: 20px; - height: 20px; - filter: brightness(0) saturate(100%) invert(40%) sepia(0%) saturate(0%) hue-rotate(0deg); - transition: filter 0.3s ease-in-out; - } - - /* Ajout de la classe active */ - .lexique-highlight-toggle.active .feutre-icon, - .lexique-highlight-toggle[data-active="true"] .feutre-icon { - filter: brightness(0) saturate(100%) invert(83%) sepia(89%) saturate(588%) hue-rotate(360deg); - } - - button.lexique-highlight-toggle .tooltip { - all: unset; - display: block; - box-sizing: border-box; - position: absolute; - bottom: 120%; - left: 50%; - transform: translateX(-50%) translateY(-5px); - background-color: rgba(0, 0, 0, 0.75); - color: #fff; - font-size: 14px; - font-weight: lighter; - padding: 6px 10px; - border-radius: 5px; - white-space: normal; - overflow-wrap: break-word; - width: 180px; - text-align: center; - opacity: 0; - transition: opacity 0.3s ease-in-out, transform 0.2s ease-in-out; - pointer-events: none; - z-index: 10; - line-height: normal; - } - - button.lexique-highlight-toggle:hover .tooltip { - opacity: 1; - transform: translateX(-50%) translateY(-5px); - } - - .lexicon-highlight { - position: relative; /* Pour positionner le conteneur de bandes en absolu */ - display: inline-block; /* Pour que le span prenne en compte les dimensions */ - padding-bottom: 4px; /* Laisser de l'espace pour les bandes */ - border-bottom: 1px dashed #666; /* Vous pouvez conserver votre bordure si besoin */ - transition: background-color 0.3s; - background-color: rgba(255, 255, 0, 0.15); - - } - - .color-bands { - position: absolute; - left: 0; - right: 0; - bottom: 0; - height: 3px; /* Ajustez la hauteur des bandes */ - display: flex; /* Pour répartir équitablement les bandes */ - } - - .color-bands div { - flex: 1; /* Chaque bande occupe une part égale */ - height: 100%; - } - - - .lexicon-section { - margin-bottom: 10px; - } - .lexicon-header { - font-weight: bold; - cursor: pointer; - padding: 5px; - background-color: #8d5c70; - border-radius: 5px; - text-align: center; - } - .lexicon-header:hover { - background-color: #dddedd; - color: #8d5c70; - } - .lexicon-content { - margin-top: 5px; - } - .lexicon-option { - margin-right: -10px; - } - #mesLexiquesList { - display: inline; - padding: 0; - align-items: center; - } - #mesLexiquesContainer h4 { - margin-bottom: 5px; - } - #definitionContainer { - background-color: #444; - padding: 10px; - border-radius: 10px; - color: white; - } - #definitionsList { - list-style: none; - padding: 0; - } - #definitionsList li { - margin-bottom: 10px; - } - .definition-source { - font-weight: bold; - color: #ffa500; - } - .modal-overlay { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background: rgba(0,0,0,0.5); - display: none; - align-items: center; - justify-content: center; - z-index: 9999; - } - .modal-content { - background: white; - color: #8d5c70; - padding: 1rem; - max-width: 600px; - max-height: 80vh; - overflow-y: auto; - border-radius: 8px; - } - .close-button { - float: right; - cursor: pointer; - font-weight: bold; - color: #666; - } - .close-button:hover { - color: #000; - } - #highlighting-options p { - margin: 5px 0; - font-size: small; - color: #333; - } - #noDefinitionsContainer { - display: block !important; - color: red !important; - font-weight: bold; - } - #messageContainer { - display: none; - text-align: center; - color: #323046; - } - </style> </head> <body> <!-- Bouton de connexion --> @@ -430,7 +29,7 @@ <div id="menu"> <div class="block-header"> <h3>Lexiques</h3> - <button class="toggle-btn">+</button> + <button class="toggle-btn">+</button> <!-- Bouton pour afficher/masquer le bloc --> </div> <div id="menuContent" class="block-content hidden"> <div id="lexiques">Chargement...</div> @@ -485,7 +84,7 @@ <ul id="wiktionnaireList"></ul> </div> - <!-- Fenêtre modale cachée --> + <!-- Fenêtre modale cachée pour afficher les détails--> <div id="modalOverlay" class="modal-overlay"> <div class="modal-content"> <span id="closeModalBtn" class="close-button">X</span> 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..aab435f4e68c12c562592d3cfcd42a873548c767 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,68 +89,78 @@ 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); - - if (!Array.isArray(lexicons) || lexicons.length === 0) { - console.warn("âš ï¸ Aucun lexique retourné par l'API pour ces paramètres."); - return {}; - } - // 2) Pour chaque lexique, on récupère ses entrées via /api/lexicon/entries/{id} - const allGraphiesByLexicon = {}; + // 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 {}; + } + + // 2) Pour chaque lexique, on récupère ses entrées via /api/lexicon/entries/{id} + 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 + const lexiconName = + lexicon.category === "User" + ? `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; + } - for (const lexicon of lexicons) { - const entries = await getLexiconEntries(authToken, lexicon.id); - // Vérification que entries est bien un tableau - if (!Array.isArray(entries)) { - console.warn(`âš ï¸ Format invalide pour les entrées du lexique ${lexicon.id}:`, entries); - continue; + 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); + return {}; } - const allGraphies = entries.map(entry => entry.graphy); - - // Création d'un libellé unique pour le lexique - const lexiconName = - lexicon.category === "User" - ? `Lexique personnel (${lexicon.user?.pseudo || "Inconnu"}) [${lexicon.id}]` - : `Lexique de groupe (${lexicon.group?.name || "Inconnu"}) [${lexicon.id}]`; - - allGraphiesByLexicon[lexiconName] = allGraphies; } - - 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); - return {}; - } -} - + // ───────────────────────────────────────────────────────────────────────────── // â–Œ Récupération de définition du Wiktionnaire // ───────────────────────────────────────────────────────────────────────────── - /** * Récupère une définition du Wiktionnaire. */ 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 +190,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 +200,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/highlighting.js b/src/utils/highlighting.js index 4a673e9a1849f7f040d23b055fd09f39948b448e..1bdcacc684a146e402ed79abb7ff072466802319 100644 --- a/src/utils/highlighting.js +++ b/src/utils/highlighting.js @@ -1,62 +1,94 @@ -// 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) { + if (window.hasRunHighlighting) { log("âš ï¸ highlighting.js déjà chargé"); return; } - window.hasRun = true; + window.hasRunHighlighting = true; // Variables internes let lexiconWordsCache = new Map(); @@ -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 + } + } + + // ─────────────────────────────── + // â–Œ 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érifier si un mot appartient à un lexique (à l'aide de la cache) + // ─────────────────────────────── + // â–Œ 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); + log("Erreur lors de l'arrêt du surlignage:", error); throw error; } } - // Mise à jour du cache des lexiques - async function updateLexiconCache() { - log("📥 updateLexiconCache - Début avec context:", { - authToken: !!window.authToken, - getAllLexiconWords: !!window.getAllLexiconWords, - activeLexiconIds: Array.from(activeLexiconIds) - }); - let allWords; - try { - if (!window.authToken) { - throw new Error("Pas de token d'authentification"); - } - if (typeof window.getAllLexiconWords !== 'function') { - log("âš ï¸ getAllLexiconWords n'est pas une fonction"); - log("Type de getAllLexiconWords:", typeof window.getAllLexiconWords); - log("Contenu de window.getAllLexiconWords:", window.getAllLexiconWords); - throw new Error("getAllLexiconWords n'est pas disponible"); - } - log("📥 Appel de getAllLexiconWords..."); - allWords = await window.getAllLexiconWords(window.authToken); - log("📠Réponse de getAllLexiconWords:", allWords); - if (!allWords || typeof allWords !== 'object') { - throw new Error(`Format de données invalide: ${JSON.stringify(allWords)}`); - } - lexiconWordsCache.clear(); - if (Object.keys(allWords).length === 0) { - log("âš ï¸ Aucun lexique reçu de getAllLexiconWords"); - return false; - } - for (const [lexiconName, words] of Object.entries(allWords)) { - if (!Array.isArray(words)) { - console.warn(`âš ï¸ Format invalide pour le lexique ${lexiconName}:`, words); - continue; - } - const lexiconId = lexiconName.match(/\[(\d+)\]$/)?.[1]; - if (!lexiconId) { - console.warn(`âš ï¸ Impossible d'extraire l'ID du lexique depuis: ${lexiconName}`); - continue; - } - log(`📎 Traitement du lexique ${lexiconName} (ID: ${lexiconId})`); - if (activeLexiconIds.has(Number(lexiconId))) { - lexiconWordsCache.set(lexiconId, new Set(words)); - log(`📖 Lexique ${lexiconId} chargé avec ${words.length} mots`); - } - } - log("✅ Cache des lexiques mis à jour:", - Object.fromEntries([...lexiconWordsCache.entries()].map(([id, words]) => [id, [...words]]))); - return true; - } catch (error) { - log("⌠Erreur dans updateLexiconCache:", { - message: error.message, - stack: error.stack, - authTokenExists: !!window.authToken, - getAllLexiconWordsType: typeof window.getAllLexiconWords, - response: allWords - }); - throw error; - } - } - - // Surlignage du contenu visible + // ─────────────────────────────── + // â–Œ 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,98 @@ 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/utils/stats.js b/src/utils/stats.js index 3ee90b95e7e2d386ac56625195c7b99ef98e9497..7f1d2b12cb6100cd7d0735a37b4904574b1560db 100644 --- a/src/utils/stats.js +++ b/src/utils/stats.js @@ -1,8 +1,8 @@ (function () { - if (window.hasRun) { + if (window.hasRunStats) { return; } - window.hasRun = true; + window.hasRunStats = true; let workerPort = null; // Port unique vers le WebWorker // ───────────────────────────────────────────────────────────────────────────── // Connexion/Transmission des données avec le WebWorker diff --git a/src/workers/pyodide_worker.js b/src/workers/pyodide_worker.js index ebd2b0b34038f576f431bed94089a006244f84f5..8131c19d0c0bfe3000271d0f01b2a70122d9a2aa 100644 --- a/src/workers/pyodide_worker.js +++ b/src/workers/pyodide_worker.js @@ -1,24 +1,26 @@ importScripts("../utils/logger.js"); 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 notifiedWords = {}; // Mots déjà notifiés (pour éviter les doublons) +let includeStopwords = false; // Stocker l'état de l'inclusion des mots outils +let stoplistsByLang = {}; // Objet stockant les stoplists par langue let userLexicons = [] //Contient les lexiques et leurs ID (lexiques personnels) let authToken = null; // Stockage local du token -let includeStopwords = false; // Stocker l'état de l'inclusion des mots outils -let stoplistsByLang = {}; // Objet stockant les stoplists par langue // --- Attente de la mise à jour de la stoplist --- let stoplistsReady = new Promise((resolve) => resolve());