From 727269c09500079ba28dcf9f889e2052020a6800 Mon Sep 17 00:00:00 2001 From: Lucie Bader <167515375+Lucie-Bdr@users.noreply.github.com> Date: Tue, 18 Feb 2025 15:26:09 +0100 Subject: [PATCH] =?UTF-8?q?R=C3=A9cup=C3=A9ration=20scripts=20Alissa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 6 + src/background/background.js | 351 ++++++++++++++--------- src/context_menu/custom_context_menu.css | 7 + src/popup/popup.html | 4 +- src/popup/popup.js | 309 ++++++++++---------- src/sidebar/sidebar.html | 8 +- src/sidebar/sidebar.js | 4 + src/utils/api.js | 3 +- src/utils/definitions.js | 159 ++++++++-- src/utils/stats.js | 281 ++++++++++++------ src/workers/pyodide_worker.js | 247 +++++++++++----- stoplist_fr.txt | 116 ++++++++ 12 files changed, 1003 insertions(+), 492 deletions(-) create mode 100644 stoplist_fr.txt diff --git a/manifest.json b/manifest.json index 4f61904..d465f43 100644 --- a/manifest.json +++ b/manifest.json @@ -64,7 +64,13 @@ "src/utils/stats.js"], "css": ["src/context_menu/custom_context_menu.css"], "run_at": "document_idle" + }, + { + "matches": ["<all_urls>"], + "js": ["src/utils/stats.js"], + "run_at": "document_end" } + ], "web_accessible_resources": [ diff --git a/src/background/background.js b/src/background/background.js index b21ad4a..65ce6f0 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -45,7 +45,7 @@ browser.storage.onChanged.addListener((changes, area) => { if (!extensionActive) { log("Token ajouté, activation automatique de l'extension."); browser.storage.local.set({ extensionActive: true }); - enableExtensionFeatures(); + enableExtensionFeatures(); browser.runtime.sendMessage({ action: "updateUI", extensionActive: true, @@ -71,90 +71,6 @@ async function refreshAllUI() { browser.runtime.sendMessage({ action: "refreshUI" }); } -// ───────────────────────────────────────────────────────────────────────────── -// Initialisation du WebWorker -// ───────────────────────────────────────────────────────────────────────────── -let worker = null; - -function initWorker() { - if (!worker) { - log("[Background] Initialisation du WebWorker..."); - try { - worker = new Worker("src/workers/pyodide_worker.js"); - worker.onmessage = (event) => { - log("[Background] Message reçu du WebWorker :", event.data); - }; - worker.onerror = (error) => { - console.error("[Background] Erreur dans le WebWorker :", error); - }; - log("[Background] WebWorker initialisé avec succès."); - } catch (error) { - console.error("[Background] Échec de l'initialisation du WebWorker :", error); - } - } -} -// Initialisation -initWorker(); -worker.postMessage({ command: "pyodide-simplemma" }); - -// ───────────────────────────────────────────────────────────────────────────── -// Écoute des messages de la popup et transmission au WebWorker -// ───────────────────────────────────────────────────────────────────────────── -browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { - log("[Background] Message reçu :", message); - - if (!worker) { - initWorker(); - } - - if (message.command === "toggle-stats") { - log(`[Background] Statistiques ${message.isActive ? "activées" : "désactivées"}`); - await browser.storage.local.set({ isTrackingActive: message.isActive }); - // Transmettre l'activation des statistiques au WebWorker - worker.postMessage({ command: "toggle-stats", isActive: message.isActive }); - } - - if (message.command === "pyodide-simplemma") { - log("[Background] Demande d'initialisation de Pyodide et Simplemma..."); - worker.postMessage({ command: "pyodide-simplemma" }); - } - - return true; -}); - -// ───────────────────────────────────────────────────────────────────────────── -// Écouter les réponses du WebWorker -// ───────────────────────────────────────────────────────────────────────────── -worker.onmessage = (event) => { - log("[Background] Message du WebWorker :", event.data); - - // Pyodide et Simplemma prêts : notifier tous les onglets - if (event.data.type === "pyodide-simplemma" && event.data.status === "success") { - browser.tabs.query({}).then((tabs) => { - tabs.forEach((tab) => { - if (!tab.url || (!tab.url.startsWith("https://") && (!tab.url.startsWith("https://")))) { - return; - } - if (tab.url.includes("sidebar.html")) { - return; - } - - try { - let response = browser.tabs.sendMessage(tab.id, { command: "pyodide-simplemma-ready" }) - if (response && typeof response.then === "function") { - response.then(() => { - }).catch((error) => { - log("[Background] Impossible d'envoyer un message à l'onglet ${tab.id} : ${error}"); - }); - } - } catch (error) { - log("[Background] Erreur lors de l'envoi d'un message à l'onglet ${tab.id} : ${error}"); - } - }); - }) - } -} - // ───────────────────────────────────────────────────────────────────────────── // Fonctions d'authentification & de redirection // ───────────────────────────────────────────────────────────────────────────── @@ -191,21 +107,20 @@ async function actuallyOpenLoginPage() { browser.runtime.sendMessage({ action: "authStatusChanged", isLoggedIn: false }); } -// Déconnecte l'utilisateur async function disconnectFromLexicalDB() { await browser.storage.local.remove("accessToken"); log("🔓 Token supprimé avec succès."); - + await browser.storage.local.remove("lexiconColors"); - + // Désactivation automatique de l'extension await browser.storage.local.set({ extensionActive: false }); - disableExtensionFeatures(); - browser.runtime.sendMessage({ - action: "updateUI", - extensionActive: false, - isTrackingActive: false, - autoAdd: false + disableExtensionFeatures(); + browser.runtime.sendMessage({ + action: "updateUI", + extensionActive: false, + isTrackingActive: false, + autoAdd: false }); setTimeout(async () => { @@ -213,7 +128,6 @@ async function disconnectFromLexicalDB() { }, 500); } -// Sauvegarde le token et ferme l'onglet de login si nécessaire async function saveToken(token) { log("✅ Sauvegarde du token :", token); await browser.storage.local.set({ accessToken: token }); @@ -253,7 +167,7 @@ async function saveToken(token) { } // ───────────────────────────────────────────────────────────────────────────── -// Gestion des messages reçus +// Gestion des messages reçus (depuis popup, etc.) // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { log("📩 Message reçu dans background.js :", message); @@ -414,65 +328,218 @@ function showInstructionPopup(details) { } // ───────────────────────────────────────────────────────────────────────────── -// Gestion de l'activation/désactivation de l'extension +// Initialisation du WebWorker // ───────────────────────────────────────────────────────────────────────────── -// === 1. Initialisation de l'état de l'extension === -async function initializeExtensionState() { - const { extensionActive } = await browser.storage.local.get("extensionActive"); +let worker = null; - if (extensionActive === undefined) { - await browser.storage.local.set({ extensionActive: true }); // Activation par défaut - log("🔄 Initialisation : extension activée par défaut."); - enableExtensionFeatures(); - } else { - log(`🔄 État actuel de l'extension : ${extensionActive ? "activée" : "désactivée"}`); - if (extensionActive) { - enableExtensionFeatures(); - } else { - disableExtensionFeatures(); +function initWorker() { + if (!worker) { + console.log("[Background] Initialisation du WebWorker..."); + try { + worker = new Worker(browser.runtime.getURL("src/workers/pyodide_worker.js")); + // Centralisation de l'écoute des messages et erreurs du Worker + worker.addEventListener("message", handleWorkerMessage); + worker.addEventListener("error", handleWorkerError); + console.log("[Background] WebWorker initialisé avec succès."); + } catch (error) { + console.error("[Background] Échec de l'initialisation du WebWorker :", error); } } } -// === 2. Gestion de l'activation/désactivation de l'extension === -browser.runtime.onMessage.addListener(async (message) => { - if (message.action === "toggleExtension") { - log(`🔄 Changement d'état de l'extension : ${message.isActive ? "activée" : "désactivée"}`); - await browser.storage.local.set({ extensionActive: message.isActive }); +function handleWorkerMessage(event) { + const data = event.data; + console.log("[Background] Message du WebWorker :", data); - if (!message.isActive) { - disableExtensionFeatures(); - } else { - enableExtensionFeatures(); + switch (data.type) { + case "pyodide-simplemma": + if (data.status === "success") { + console.log("[Background] Pyodide et Simplemma prêts. Mise à jour de l'état."); + browser.storage.local.set({ pyodideSimplemmaReady: true }); + checkAndUpdateTracking(); + } + break; + case "update-frequencies": + console.log("[Background] Mise à jour des fréquences :", data.frequencies); + notifyAllTabs({ command: "update-frequencies", frequencies: data.frequencies }); + // Si un port stats est connecté, vous pouvez lui transmettre également : + // port.postMessage({ command: "update-frequencies", frequencies: data.frequencies }); + browser.storage.local.set({ lemmaFrequencies: data.frequencies }); + break; + case "threshold-exceeded": + console.log("[Background] Mots dépassant le seuil :", data.wordsAboveThreshold); + notifyAllTabs({ command: "threshold-exceeded", wordsAboveThreshold: data.wordsAboveThreshold }); + break; + default: + console.warn("[Background] Message non traité du Worker :", data); + break; + } +} + +function handleWorkerError(error) { + console.error("[Background] Erreur dans le WebWorker :", error); +} + +// Initialisation du worker dès le démarrage +initWorker(); + +// ───────────────────────────────────────────────────────────────────────────── +// Écoute des messages de la popup et transmission au WebWorker +// ───────────────────────────────────────────────────────────────────────────── +browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { + console.log("[Background] Message reçu :", message); + + if (!worker) { + initWorker(); + } + + if (message.command === "toggle-stats") { + console.log(`[Background] Statistiques ${message.isActive ? "activées" : "désactivées"}`); + const { isActive } = message; + await browser.storage.local.set({ isTrackingActive: isActive }); + if (!isActive) { + await browser.storage.local.set({ autoAdd: false }); } - browser.runtime.sendMessage({ action: "updateUI" }); + checkAndUpdateTracking(); } + + if (message.command === "pyodide-simplemma") { + console.log("[Background] Demande d'initialisation de Pyodide et Simplemma..."); + worker.postMessage({ command: "pyodide-simplemma" }); + } + + return true; }); -// === 3. Fonction pour désactiver les fonctionnalités de l'extension === -async function disableExtensionFeatures() { - log("Désactivation des fonctionnalités de l'extension."); - browser.runtime.sendMessage({ - action: "updateUI", - autoAdd: false, - isTrackingActive: false, - areStatsActive: false - }); - // Suppression d'éventuelles actions spécifiques à la sidebar - log("Les fonctionnalités de la sidebar ne sont plus utilisées."); +// ───────────────────────────────────────────────────────────────────────────── +// Chargement et sauvegarde des fréquences stockées +// ───────────────────────────────────────────────────────────────────────────── +async function loadStoredFrequencies() { + const { storedFrequencies } = await browser.storage.local.get("storedFrequencies"); + return storedFrequencies || {}; } -// === 4. Fonction pour activer les fonctionnalités de l'extension === -async function enableExtensionFeatures() { - log("Réactivation des fonctionnalités de l'extension."); - browser.runtime.sendMessage({ - action: "updateUI", - autoAdd: true, - isTrackingActive: true, - areStatsActive: true - }).catch(err => { - console.warn("Aucun récepteur pour le message updateUI :", err); +let storedFrequencies = {}; + +loadStoredFrequencies().then(frequencies => { + storedFrequencies = frequencies; + console.log("[Background] Fréquences initialisées :", storedFrequencies); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Statistiques : Vérification et activation/désactivation du tracking +// ───────────────────────────────────────────────────────────────────────────── +async function checkAndUpdateTracking() { + const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); + if (isTrackingActive && pyodideSimplemmaReady) { + console.log("[Background] Activation du tracking."); + notifyAllTabs({ command: "activate-stats" }); + } else { + console.log("[Background] Désactivation du tracking."); + notifyAllTabs({ command: "deactivate-stats" }); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Statistiques : Gestion des onglets +// ───────────────────────────────────────────────────────────────────────────── +async function notifyAllTabs(message) { + browser.tabs.query({}).then((tabs) => { + tabs.forEach((tab) => { + browser.tabs.sendMessage(tab.id, message) + .catch((error) => console.warn(`[Background] Impossible d'envoyer un message à l'onglet ${tab.id} : ${error}`)); + }); }); } -initializeExtensionState(); +// ───────────────────────────────────────────────────────────────────────────── +// Statistiques : Écoute des modifications du stockage et mise à jour du tracking +// ───────────────────────────────────────────────────────────────────────────── +browser.storage.onChanged.addListener(async (changes, area) => { + if (area === "local" && (changes.isTrackingActive || changes.pyodideSimplemmaReady)) { + checkAndUpdateTracking(); + } + if (area === "local" && (changes.accessToken || changes.threshold || changes.trackedLanguages || changes.autoAdd)) { + console.log("[Background] Mise à jour des préférences détectée."); + const { accessToken, trackedLanguages, threshold, autoAdd } = await browser.storage.local.get([ + "accessToken", "trackedLanguages", "threshold", "autoAdd" + ]); + const isAuthenticated = !!accessToken; + worker.postMessage({ + command: "update-preferences", + isAuthenticated, + trackedLanguages: trackedLanguages || [], + threshold: threshold || 10, + autoAdd: autoAdd || false + }); + } +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Écoute de l'inscription de stats.js +// ───────────────────────────────────────────────────────────────────────────── +browser.runtime.onMessage.addListener(async (message, sender) => { + if (message.command === "register-stats-script") { + console.log("[Background] stats.js s'est enregistré."); + const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); + if (isTrackingActive && pyodideSimplemmaReady) { + browser.tabs.sendMessage(sender.tab.id, { command: "activate-stats" }) + .catch((error) => console.warn(`[Background] Impossible d'envoyer activate-stats à ${sender.tab.id} : ${error}`)); + } + } +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Connexion entre stats.js et le Worker via un port dédié +// ───────────────────────────────────────────────────────────────────────────── +browser.runtime.onConnect.addListener((port) => { + console.log("[Background] Connexion établie :", port.name); + if (port.name === "stats-worker-port") { + // Redirige les messages de stats.js vers le Worker + port.onMessage.addListener((message) => { + console.log("[Background] Message reçu de stats.js :", message); + worker.postMessage(message); + }); + + // Transmettre les réponses du Worker à stats.js via le port + worker.addEventListener("message", (event) => { + const data = event.data; + if (data.type === "update-frequencies") { + port.postMessage({ command: "update-frequencies", frequencies: data.frequencies }); + } + if (data.type === "threshold-exceeded") { + port.postMessage({ command: "threshold-exceeded", wordsAboveThreshold: data.wordsAboveThreshold }); + } + }); + } +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Stoplists : Chargement et envoi au Worker +// ───────────────────────────────────────────────────────────────────────────── +let stoplistFr = []; + +function loadStoplist() { + fetch(browser.runtime.getURL("stoplist_fr.txt")) + .then(response => response.text()) + .then(text => { + stoplistFr = text.split("\n").map(word => word.trim()); + console.log("[Background] Stoplist chargée :", stoplistFr); + sendStoplistToWorker(); + }) + .catch(error => console.error("[Background] Erreur lors du chargement de la stoplist :", error)); +} + +function sendStoplistToWorker() { + console.log("[Background] Envoi de la stoplist au Worker..."); + worker.postMessage({ command: "update-stoplist", stoplist: stoplistFr }); +} + +browser.runtime.onStartup.addListener(loadStoplist); +browser.runtime.onInstalled.addListener(loadStoplist); + +// ───────────────────────────────────────────────────────────────────────────── +// (Code commenté concernant l'activation/désactivation de l'analyse) +// ───────────────────────────────────────────────────────────────────────────── +// async function initializeExtensionState() { ... } +// initializeExtensionState(); diff --git a/src/context_menu/custom_context_menu.css b/src/context_menu/custom_context_menu.css index 622a8c6..82c76a1 100644 --- a/src/context_menu/custom_context_menu.css +++ b/src/context_menu/custom_context_menu.css @@ -1,4 +1,11 @@ +@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 === */ #whiteBox { diff --git a/src/popup/popup.html b/src/popup/popup.html index f993c52..b9c17fd 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -8,8 +8,8 @@ <style> @font-face { font-family: 'Luciole'; - src: url('chemin/vers/Luciole-Regular.woff2') format('woff2'), - url('chemin/vers/Luciole-Regular.woff') format('woff'); + 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; } diff --git a/src/popup/popup.js b/src/popup/popup.js index 2fdf2e3..8ccc3fc 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -1,45 +1,60 @@ log("✅ popup.js chargé avec succès !"); -// === 1. Mise à jour du bouton Connexion/Déconnexion === -async function updateConnectionButton() { +// ========================== +// Fonctions utilitaires +// ========================== +async function getAccessToken() { const { accessToken } = await browser.storage.local.get("accessToken"); + return accessToken; +} + +// ========================== +// Gestion de la connexion +// ========================== +async function updateConnectionButton() { + const accessToken = await getAccessToken(); const button = document.getElementById("auth-button"); - if (button) { - if (accessToken) { - button.textContent = "Se déconnecter"; - button.title = "En vous déconnectant, vous perdrez l'accès à vos lexiques personnels, ainsi que les fonctionnalités d'ajout automatique et de statistiques d'utilisation."; - } else { - button.textContent = "Se connecter"; - button.title = "En vous connectant, vous pourrez accéder à vos lexiques personnels, ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation."; - } - button.onclick = async () => { - await browser.runtime.sendMessage({ action: "toggleAuth" }); - }; - } else { + if (!button) { console.error("⌠Le bouton de connexion n'a pas été trouvé."); + return; } + + if (accessToken) { + button.textContent = "Se déconnecter"; + button.title = "En vous déconnectant, vous perdrez l'accès à vos lexiques personnels, ainsi que les fonctionnalités d'ajout automatique et de statistiques d'utilisation."; + } else { + button.textContent = "Se connecter"; + button.title = "En vous connectant, vous pourrez accéder à vos lexiques personnels, ainsi qu'aux fonctionnalités d'ajout automatique et de statistiques d'utilisation."; + } + + button.onclick = async () => { + await browser.runtime.sendMessage({ action: "toggleAuth" }); + }; } -// === 2. Mise à jour dynamique de la sélection des langues === +// ========================== +// Gestion de la sélection des langues +// ========================== async function updateLanguageSelection() { const languageSelection = document.getElementById("language-selection"); languageSelection.innerHTML = "<p id='loading-languages' style='color: gray;'>Chargement...</p>"; - const { accessToken } = await browser.storage.local.get("accessToken"); + const accessToken = await getAccessToken(); if (!accessToken) { languageSelection.innerHTML = "<p style='color: red;'>Veuillez vous connecter.</p>"; return; } + // Utilisation de la fonction de récupération de lexiques + // Choisissez ici entre getLexicons et getUserLexicons en fonction de votre implémentation const lexicons = await getLexicons(accessToken); const userLanguages = [...new Set(lexicons.map(lex => lex.language))]; // Récupérer les langues suivies depuis le stockage const { trackedLanguages } = await browser.storage.local.get("trackedLanguages") || { trackedLanguages: [] }; - languageSelection.innerHTML = ""; - + languageSelection.innerHTML = ""; if (userLanguages.length === 0) { languageSelection.innerHTML = "<p style='color: red;'>Aucun lexique personnel trouvé.</p>"; return; @@ -50,64 +65,62 @@ async function updateLanguageSelection() { 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); } -// === 3. Mise à jour des options utilisateur et des statistiques === +// ========================== +// Gestion des options et des statistiques +// ========================== async function updateOptionsUI() { - const { accessToken, extensionActive } = await browser.storage.local.get(["accessToken", "extensionActive"]); + const accessToken = await getAccessToken(); const isLoggedIn = !!accessToken; - - const toggleStatsBtn = document.getElementById("toggleStatsBtn"); const statsOptions = document.getElementById("stats-options"); const autoAddContainer = document.getElementById("auto-add")?.parentElement; const autoAddCheckbox = document.getElementById("auto-add"); const autoAddOptions = document.getElementById("auto-add-options"); const thresholdInput = document.getElementById("threshold"); const saveOptionsBtn = document.getElementById("save-options"); + const toggleStatsBtn = document.getElementById("toggleStatsBtn"); const openStats = document.getElementById("open-stats"); + // Affichage de l'option "Ajout automatique" selon la connexion if (autoAddContainer) { autoAddContainer.style.display = isLoggedIn ? "block" : "none"; } + // Chargement des préférences const { isTrackingActive, autoAdd, threshold } = await browser.storage.local.get([ "isTrackingActive", "autoAdd", "threshold" ]) || { isTrackingActive: false }; + // Gestion du bouton de statistiques if (toggleStatsBtn) { if (!isLoggedIn) { - openStats.style.display = "none"; - } else { - openStats.style.display = "block"; - } - if (!accessToken) { - toggleStatsBtn.textContent = "Activer les statistiques"; toggleStatsBtn.style.opacity = "0.5"; toggleStatsBtn.title = "Connectez-vous pour activer les statistiques"; + if (openStats) openStats.style.display = "none"; } else { toggleStatsBtn.style.opacity = "1"; - toggleStatsBtn.textContent = isTrackingActive ? "Désactiver les statistiques" : "Activer les statistiques"; toggleStatsBtn.title = ""; + if (openStats) openStats.style.display = "block"; } + toggleStatsBtn.textContent = isTrackingActive ? "Désactiver les statistiques" : "Activer les statistiques"; } + // Affichage des options statistiques if (statsOptions) { - statsOptions.classList.toggle("hidden", !extensionActive || !isTrackingActive); + statsOptions.classList.toggle("hidden", !isTrackingActive); } if (isLoggedIn) { @@ -136,96 +149,38 @@ async function updateOptionsUI() { } } -// === 4. Gestion des événements pour options/statistiques === -document.getElementById("toggleStatsBtn").addEventListener("click", async () => { - const { accessToken } = await browser.storage.local.get("accessToken"); - if (!accessToken) { - return; - } - const current = await browser.storage.local.get("isTrackingActive"); - const newState = !(current.isTrackingActive); - - await browser.storage.local.set({ isTrackingActive: newState }); - - if (!newState) { - await browser.storage.local.set({ autoAdd: false }); - document.getElementById("auto-add").checked = false; - document.getElementById("auto-add-options").classList.add("hidden"); - document.getElementById("save-options").classList.add("hidden"); - } - - document.getElementById("toggleStatsBtn").textContent = newState ? "Désactiver les statistiques" : "Activer les statistiques"; - document.getElementById("stats-options").classList.toggle("hidden", !newState); - - browser.runtime.sendMessage({ command: "toggle-stats", isActive: newState }); - - if (newState) { - log("[Popup] Demande d'initialisation de Pyodide..."); - browser.runtime.sendMessage({ command: "init-pyodide" }); - } -}); - -document.getElementById("auto-add").addEventListener("change", async () => { - const isChecked = document.getElementById("auto-add").checked; - document.getElementById("auto-add-options").classList.toggle("hidden", !isChecked); - document.getElementById("save-options").classList.toggle("hidden", !isChecked); - - if (!isChecked) { - await browser.storage.local.set({ autoAdd: false }); - } -}); - -document.getElementById("save-options").addEventListener("click", async () => { - const autoAdd = document.getElementById("auto-add").checked; - const threshold = parseInt(document.getElementById("threshold").value, 10); - const selectedLanguages = Array.from(document.querySelectorAll("#language-selection .lang-option.selected")) - .map(option => option.dataset.value); - - await browser.storage.local.set({ - autoAdd, - threshold, - trackedLanguages: selectedLanguages - }); - - log("Options sauvegardées :", { autoAdd, threshold, trackedLanguages: selectedLanguages }); -}); - -document.getElementById("open-stats").addEventListener("click", function() { - window.open("stats.html", "_blank"); -}); - - -// === 5. Gestion de l'activation/désactivation de l'analyse === +// ========================== +// Gestion de l'activation/désactivation de l'extension +// ========================== async function updateExtensionToggleButton() { const { accessToken, extensionActive } = await browser.storage.local.get(["accessToken", "extensionActive"]); const toggleButton = document.getElementById("toggleExtensionBtn"); - if (toggleButton) { - if (!accessToken) { - toggleButton.textContent = "Activer l'analyse"; - toggleButton.style.opacity = "0.5"; - toggleButton.title = "Connectez-vous pour activer l'analyse"; - } else { - toggleButton.style.opacity = "1"; - toggleButton.textContent = extensionActive ? "Désactiver l'analyse" : "Activer l'analyse"; - toggleButton.title = ""; - } + if (!toggleButton) { + console.error("⌠Le bouton d'activation de l'analyse n'a pas été trouvé."); + return; + } - if (!toggleButton.dataset.listenerAdded) { - toggleButton.addEventListener("click", handleToggleExtension); - toggleButton.dataset.listenerAdded = "true"; - } + if (!accessToken) { + toggleButton.textContent = "Activer l'analyse"; + toggleButton.style.opacity = "0.5"; + toggleButton.title = "Connectez-vous pour activer l'analyse"; } else { - console.error("Le bouton d'activation de l'anlyse n'a pas été trouvé."); + toggleButton.style.opacity = "1"; + toggleButton.textContent = extensionActive ? "Désactiver l'analyse" : "Activer l'analyse"; + toggleButton.title = ""; + } + + // Ajout d'un écouteur unique + if (!toggleButton.dataset.listenerAdded) { + toggleButton.addEventListener("click", handleToggleExtension); + toggleButton.dataset.listenerAdded = "true"; } } -// Gestion du clic sur le bouton d'activation/désactivation -function handleToggleExtension(event) { - browser.storage.local.get("accessToken").then(({ accessToken }) => { - if (!accessToken) { - return; - } +function handleToggleExtension() { + getAccessToken().then(accessToken => { + if (!accessToken) return; proceedToggleExtension(); }); } @@ -236,8 +191,8 @@ async function proceedToggleExtension() { isTrackingActive: false }); const newState = !extensionActive; - await browser.storage.local.set({ extensionActive: newState }); + const toggleButton = document.getElementById("toggleExtensionBtn"); if (toggleButton) { toggleButton.textContent = newState ? "Désactiver l'analyse" : "Activer l'analyse"; @@ -247,52 +202,94 @@ async function proceedToggleExtension() { if (!newState) { await browser.storage.local.set({ isTrackingActive: false }); - if (isTrackingActive) { - window.open("stats.html", "_blank"); - } + // Si les statistiques étaient activées, ouvrir la page stats + if (isTrackingActive) window.open("stats.html", "_blank"); browser.runtime.sendMessage({ action: "closeSidebarBlocks" }); } await updateOptionsUI(); } -// === 6. Mise à jour de l'UI du popup en fonction d'un message === -async function updatePopupUI(message) { - log("🔄 Mise à jour du popup avec :", message); +// ========================== +// Écouteurs d'événements +// ========================== +document.addEventListener("DOMContentLoaded", async () => { + await updateConnectionButton(); + await updateOptionsUI(); + await updateLanguageSelection(); + await updateExtensionToggleButton(); +}); - const statsOptions = document.getElementById("stats-options"); - const autoAddCheckbox = document.getElementById("auto-add"); - const autoAddOptions = document.getElementById("auto-add-options"); +// Gestion des statistiques et options +document.getElementById("toggleStatsBtn")?.addEventListener("click", async () => { + const accessToken = await getAccessToken(); + if (!accessToken) return; - if (message.isTrackingActive !== undefined) { - if (statsOptions) { - statsOptions.classList.toggle("hidden", !message.isTrackingActive); - } else { - console.warn("âš ï¸ Ã‰lément #stats-options introuvable."); - } + const current = await browser.storage.local.get("isTrackingActive"); + const newState = !current.isTrackingActive; + await browser.storage.local.set({ isTrackingActive: newState }); + + // Si désactivation, désactiver aussi l'ajout automatique + if (!newState) { + await browser.storage.local.set({ autoAdd: false }); + document.getElementById("auto-add").checked = false; + document.getElementById("auto-add-options").classList.add("hidden"); + document.getElementById("save-options").classList.add("hidden"); } - if (message.autoAdd !== undefined) { - if (autoAddCheckbox) { - autoAddCheckbox.checked = message.autoAdd; - } else { - console.warn("âš ï¸ Ã‰lément #auto-add introuvable."); - } + document.getElementById("toggleStatsBtn").textContent = newState ? "Désactiver les statistiques" : "Activer les statistiques"; + document.getElementById("stats-options").classList.toggle("hidden", !newState); - if (autoAddOptions) { - autoAddOptions.classList.toggle("hidden", !message.autoAdd); - } else { - console.warn("âš ï¸ Ã‰lément #auto-add-options introuvable."); - } + // Envoi du message au background pour le changement de tracking + browser.runtime.sendMessage({ command: "toggle-stats", isActive: newState }); + if (newState) { + log("[Popup] Demande d'initialisation de Pyodide et Simplemma"); + // Garder la commande de file 2 pour l'initialisation de Pyodide/Simplemma + browser.runtime.sendMessage({ command: "pyodide-simplemma" }); } +}); - const toggleButton = document.getElementById("toggleExtensionBtn"); - if (toggleButton && message.extensionActive !== undefined) { - toggleButton.textContent = message.extensionActive ? "Désactiver l'analyse" : "Activer l'analyse"; +// Gestion du changement de l'option d'ajout automatique +document.getElementById("auto-add")?.addEventListener("change", async () => { + const isChecked = document.getElementById("auto-add").checked; + document.getElementById("auto-add-options").classList.toggle("hidden", !isChecked); + document.getElementById("save-options").classList.toggle("hidden", !isChecked); + if (!isChecked) { + await browser.storage.local.set({ autoAdd: false }); } -} +}); -// === 7. Écoute des messages depuis le background === +// Sauvegarde des options utilisateur +document.getElementById("save-options")?.addEventListener("click", async () => { + const autoAdd = document.getElementById("auto-add").checked; + const threshold = parseInt(document.getElementById("threshold").value, 10); + const selectedLanguages = Array.from(document.querySelectorAll("#language-selection .lang-option.selected")) + .map(option => option.dataset.value); + + // Vérification : si auto-add est activé, au moins une langue doit être sélectionnée + const errorMessage = document.getElementById("error-message"); + if (autoAdd && selectedLanguages.length === 0) { + errorMessage?.classList.remove("hidden"); + return; + } + errorMessage?.classList.add("hidden"); + + await browser.storage.local.set({ + autoAdd, + threshold, + trackedLanguages: selectedLanguages + }); + log("Options sauvegardées :", { autoAdd, threshold, trackedLanguages: selectedLanguages }); +}); + +// Bouton pour ouvrir la page des statistiques +document.getElementById("open-stats")?.addEventListener("click", () => { + window.open("stats.html", "_blank"); +}); + +// ========================== +// Réception des messages du background +// ========================== browser.runtime.onMessage.addListener(async (message) => { log("📩 Message reçu dans popup.js :", message); @@ -301,12 +298,14 @@ browser.runtime.onMessage.addListener(async (message) => { await updateConnectionButton(); await updateOptionsUI(); await updateLanguageSelection(); - updatePopupUI(message); + // Possibilité de mettre à jour d'autres éléments via le message } else if (message.action === "showNotification") { - // Logique pour afficher une notification si nécessaire + // Gestion des notifications si besoin + showNotification(message.text); } }); +// Actualisation de l'UI en cas de changement dans le stockage local browser.storage.onChanged.addListener((changes, area) => { if (area === "local" && changes.accessToken) { updateConnectionButton(); @@ -314,7 +313,9 @@ browser.storage.onChanged.addListener((changes, area) => { } }); -// === 8. Notification d'activation/désactivation (optionnelle) === +// ========================== +// Gestion de l'affichage de notifications (optionnelle) +// ========================== function showNotification(message) { const notificationBox = document.getElementById("extension-notification"); const notificationText = document.getElementById("notification-text"); @@ -323,7 +324,6 @@ function showNotification(message) { if (notificationBox && notificationText && closeButton) { notificationText.textContent = message; notificationBox.classList.remove("hidden"); - closeButton.addEventListener("click", () => { notificationBox.classList.add("hidden"); }, { once: true }); @@ -331,12 +331,3 @@ function showNotification(message) { console.error("⌠Impossible d'afficher la notification : élément manquant."); } } - -// === 9. Initialisation lors du DOMContentLoaded === -document.addEventListener("DOMContentLoaded", async () => { - await updateConnectionButton(); - await updateOptionsUI(); - await updateLanguageSelection(); - await updateExtensionToggleButton(); -}); - diff --git a/src/sidebar/sidebar.html b/src/sidebar/sidebar.html index eb7d5ee..daea55d 100644 --- a/src/sidebar/sidebar.html +++ b/src/sidebar/sidebar.html @@ -9,8 +9,14 @@ <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; diff --git a/src/sidebar/sidebar.js b/src/sidebar/sidebar.js index 1233404..48b3e37 100644 --- a/src/sidebar/sidebar.js +++ b/src/sidebar/sidebar.js @@ -558,6 +558,10 @@ browser.runtime.onMessage.addListener(async (message) => { case "pyodide-simplemma-ready": return; + case "saveToken": + authToken = message.token; + break; + case "closeSidebarBlocks": closeBlock("menuContent"); closeBlock("etatContent"); diff --git a/src/utils/api.js b/src/utils/api.js index 05d106e..73d7fd0 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,5 +1,6 @@ log("✅ api.js chargé correctement"); window.authToken = null; + // ───────────────────────────────────────────────────────────────────────────── // â–Œ Sélection de texte sur la page // ───────────────────────────────────────────────────────────────────────────── @@ -218,5 +219,5 @@ window.getLexiconEntries = getLexiconEntries; window.getAllLexiconWords = getAllLexiconWords; window.getWiktionaryDefinition = getWiktionaryDefinition; window.AddWord = AddWord; -window.getLexiconsColors = getLexiconsColors; + diff --git a/src/utils/definitions.js b/src/utils/definitions.js index 7a1f7d0..fd57303 100644 --- a/src/utils/definitions.js +++ b/src/utils/definitions.js @@ -125,46 +125,149 @@ async function fetchLexiconDefinitions(word) { * Récupère la définition d'un mot depuis le Wiktionnaire (fr). * Retourne un tableau d'objets : [{ source: 'Wiktionnaire', text: '...' }] */ -async function fetchWiktionaryDefinition(word) { +// async function fetchWiktionaryDefinition(word) { +// try { +// log(`🔠Requête Wiktionnaire pour "${word}"...`); +// if (!word || word.trim() === "") { +// throw new Error("âš ï¸ Mot vide, impossible d'envoyer la requête."); +// } +// const wiktionaryURL = `https://fr.wiktionary.org/w/api.php?action=query&format=json&origin=*&prop=extracts&explaintext=true&redirects=1&titles=${encodeURIComponent(word)}`; +// const response = await fetch(wiktionaryURL); +// if (!response.ok) { +// throw new Error(`⌠Erreur API Wiktionnaire: ${response.statusText}`); +// } +// const data = await response.json(); +// log("📖 Réponse API (Wiktionnaire) :", data); + +// const pages = data.query?.pages; +// const page = pages ? Object.values(pages)[0] : null; + +// const definitionText = page && page.extract +// ? page.extract.trim() +// : "âš ï¸ Aucune définition trouvée sur le Wiktionnaire."; + +// log("🌠Définition Wiktionnaire extraite :", definitionText); + +// return [ +// { +// source: "Wiktionnaire", +// text: definitionText +// } +// ]; +// } catch (error) { +// console.error("⌠Erreur Wiktionnaire :", error); +// return [ +// { +// source: "Wiktionnaire", +// text: "âš ï¸ Erreur lors de la récupération sur le Wiktionnaire." +// } +// ]; +// } +// } + +async function wikiApiResponse(word) { + const result = await browser.storage.local.get("accessToken"); + authToken = result.accessToken; + // Construire l'URL de l'API avec le mot sélectionné + const wiktionaryApiUrl = `https://babalex.lezinter.net/api/wiktionary/search?graphy=${encodeURIComponent(word)}&language=fr`; + try { - log(`🔠Requête Wiktionnaire pour "${word}"...`); - if (!word || word.trim() === "") { - throw new Error("âš ï¸ Mot vide, impossible d'envoyer la requête."); - } - const wiktionaryURL = `https://fr.wiktionary.org/w/api.php?action=query&format=json&origin=*&prop=extracts&explaintext=true&redirects=1&titles=${encodeURIComponent(word)}`; - const response = await fetch(wiktionaryURL); + const response = await fetch(wiktionaryApiUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${authToken}`, + 'Content-Type': 'application/json', + }, + }); + if (!response.ok) { - throw new Error(`⌠Erreur API Wiktionnaire: ${response.statusText}`); + throw new Error(`Erreur lors de la récupération de la définition depuis le Wiktionnaire : ${response.statusText}`); } - const data = await response.json(); - log("📖 Réponse API (Wiktionnaire) :", data); - const pages = data.query?.pages; - const page = pages ? Object.values(pages)[0] : null; - const definitionText = page && page.extract - ? page.extract.trim() - : "âš ï¸ Aucune définition trouvée sur le Wiktionnaire."; + const data = await response.json(); + console.log(`Résultats du Wiktionnaire pour le mot "${word}" :`, data); + return data; + } catch (error) { + console.error('Erreur lors de la récupération de la définition depuis le Wiktionnaire :', error); + throw error; + } +} - log("🌠Définition Wiktionnaire extraite :", definitionText); +function formatDefinitionData(apiResponse) { + let formattedData = { + word: apiResponse[0]?.id.split("-").slice(2).join("-") || "", + pronunciations: [], + definitions: [], + examples: [], + translations: {}, + }; + + apiResponse.forEach(entry => { + const wordData = entry[entry.id.split(".").slice(-1)[0]]; // Accéder aux données via la clé dynamique + + // Ajout des prononciations + if (wordData.pronunciations) { + formattedData.pronunciations.push(...wordData.pronunciations); + } - return [ - { - source: "Wiktionnaire", - text: definitionText + // Ajout des définitions + if (wordData.senses) { + for (let senseKey in wordData.senses) { + let sense = wordData.senses[senseKey]; + if (sense.Definitions) { + formattedData.definitions.push(...sense.Definitions.map(d => d.definition)); + } + if (sense.Examples) { + formattedData.examples.push(...sense.Examples.map(e => e.example)); + } + } } - ]; - } catch (error) { - console.error("⌠Erreur Wiktionnaire :", error); - return [ - { - source: "Wiktionnaire", - text: "âš ï¸ Erreur lors de la récupération sur le Wiktionnaire." + + // Ajout des traductions + if (entry.translations) { + entry.translations.forEach(translation => { + if (!formattedData.translations[translation.lang_name]) { + formattedData.translations[translation.lang_name] = []; + } + formattedData.translations[translation.lang_name].push(translation.sense); + }); } - ]; + }); + + return formattedData; +} + +async function fetchWiktionaryDefinition(word) { + try { + console.log(` Recherche de la définition pour : ${word}`); + + // Récupération des données depuis l'API + const apiResponse = await wikiApiResponse(word); + console.log("Réponse brute de l'API :", apiResponse); + + if (!Array.isArray(apiResponse) || apiResponse.length === 0) { + console.warn(`Aucune définition trouvée pour "${word}"`); + return []; // Retourne un tableau vide si aucune définition + } + + // Formatage des données + const formattedData = formatDefinitionData(apiResponse); + console.log("Données formatées :", formattedData); + + return [ + { + source: "Wiktionnaire", + text: formattedData.definitions.length > 0 ? formattedData.definitions.join(" | ") : "âš ï¸ Aucune définition disponible." + } + ]; + } catch (error) { + console.error("Erreur lors de la récupération de la définition :", error); + return [{ source: "Wiktionnaire", text: "Erreur lors de la récupération sur le Wiktionnaire." }]; } } + // ───────────────────────────────────────────────────────────────────────────── // â–Œ Affichage des définitions dans la barre latérale // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/utils/stats.js b/src/utils/stats.js index 4714001..213f34a 100644 --- a/src/utils/stats.js +++ b/src/utils/stats.js @@ -1,96 +1,232 @@ -log("Script stats.js chargé !"); (function () { if (window.hasRun) { return; } window.hasRun = true; + let workerPort = null; // Port unique vers le WebWorker + // ───────────────────────────────────────────────────────────────────────────── + // Connexion/Transmission des données avec le WebWorker + // ───────────────────────────────────────────────────────────────────────────── + //Connexion au port + function connectToWorker() { + if (!workerPort) { + // console.log("[Stats] Connexion au WebWorker..."); + workerPort = browser.runtime.connect({ name: "stats-worker-port" }); + workerPort.onMessage.addListener((message) => { + console.log("[Stats] Message reçu du Worker :", message); + if (message.command === "update-frequencies") { + console.log("[Stats] Fréquences mises à jour :", message.frequencies); + } + if (message.command === "threshold-exceeded") { + console.log("[Stats] Mots dépassant le seuil :", message.wordsAboveThreshold); + let alertMessage = "Nouveaux mots dépassant le seuil :\n"; + for (const [lang, words] of Object.entries(message.wordsAboveThreshold)) { + alertMessage += `\n🔹 ${lang.toUpperCase()} : ${words.join(", ")}`; + } + alert(alertMessage); + } + }); + workerPort.onDisconnect.addListener(() => { + // console.log("[Stats] Déconnexion du WebWorker."); + workerPort = null; + }); + } + } + //Fonction pour envoyer le texte directement au Worker + function sendTextToWorker(text) { + if (!workerPort) { + connectToWorker(); + } + if (workerPort) { + console.log("[Stats] Envoi du texte au Worker :", text); + workerPort.postMessage({ command: "process-text", text: text }); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // Gestion des messages envoyés depuis le background + // ───────────────────────────────────────────────────────────────────────────── + browser.runtime.onMessage.addListener((message) => { + console.log("[Stats] Message reçu :", message); + if (message.command === "activate-stats") { + startTracking(); + } + if (message.command === "deactivate-stats") { + stopTracking(); + } + }); - let isTrackingActive = false; + //Enregistrement manuel auprès du background pour écouter activate-stats + browser.runtime.sendMessage({ command: "register-stats-script" }); + + // ───────────────────────────────────────────────────────────────────────────── + // Extraction du texte sur les pages + // ───────────────────────────────────────────────────────────────────────────── let scrollListenerAttached = false; - const seenContent = new Set(); - let simplemmaReady = false; + const READING_SPEED_MS_PER_WORD = 250; // Seuil de lecture + // Stockage des chronos et des éléments lus + const readingTimers = new Map(); + const readContent = new Set(); + let userIsActive = false; // Indique si l'utilisateur est actif sur la page + + // Détecte l'activité utilisateur pour éviter les fausses lectures + document.addEventListener("mousemove", () => userIsActive = true); + document.addEventListener("keydown", () => userIsActive = true); + setInterval(() => userIsActive = false, 60000); // Reset toutes les minutes + + // Arrête tous les chronomètres lorsque l'utilisateur change d'onglet + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + // console.log("[Stats] Changement d'onglet détecté"); + resetAllTimers(); + } + }); /** - * Fonction pour extraire le texte visible et l'envoyer au background script. + * Fonction pour extraire le texte visible */ function trackVisibleContent() { - // pas de tracking tant que Pyodide n’est pas prêt - if (!simplemmaReady) { - log("[Stats] Pyodide n'est pas encore prêt, attente..."); - return; - } + let selectors = "p, h1, h2, h3, h4, h5, h6, ul, ol, li, table, tr, td, th, blockquote, span, b"; - let selectors = "p, h1, h2, h3, h4, h5, h6, ul, ol, li, table, tr, td, th, blockquote"; - // Sélecteurs spécifiques à exclure sur Wikipedia et d'autres sites let excludeSelectors = [ - "#p-lang", // Liste des langues sur Wikipedia - "#footer", // Pied de page - ".navbox", // Boîtes de navigation - ".infobox", // Infobox - ".sidebar", // Sidebars en général - "script", // Scripts JS - "style" // Styles CSS + "#p-lang", "#footer", ".navbox", ".infobox", ".sidebar", "script", "style", ".interlanguage-link" ]; - + + document.querySelectorAll(selectors).forEach((element) => { if (excludeSelectors.some(sel => element.closest(sel))) { - return; // On ignore cet élément + return; // Ignore les éléments exclus } const rect = element.getBoundingClientRect(); - if (rect.top >= 0 && rect.bottom <= window.innerHeight) { - let text = element.innerText - text = cleanText(text); // nettoyage du texte avec cleanText - if (text.length > 10 && !seenContent.has(text)) { - seenContent.add(text); - log("[Stats] Envoi du texte filtré au background.js :", text); + const isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight; + let text = cleanText(element.innerText); + let wordCount = text.split(/\s+/).length; + let minReadTime = wordCount * READING_SPEED_MS_PER_WORD; + + if (text.length < 3) return; // Ignore les petits éléments - browser.runtime.sendMessage({ command: "process-text", text: text }); + if (isVisible) { + if (!readContent.has(text)) { + startReadingTimer(element, text, minReadTime); } + highlightElement(element, true); + } else { + stopReadingTimer(element, text); + highlightElement(element, false); } }); } + + /** + * Ajoute une bordure rouge autour de l'élément + */ + function highlightElement(element, shouldHighlight) { + if (shouldHighlight) { + element.style.outline = "3px solid red"; + } else { + element.style.outline = ""; + removeReadingIndicator(element); + } + } + + /** + * Démarre un chronomètre pour vérifier si l'élément est lu + */ + function startReadingTimer(element, text, minReadTime) { + if (!readingTimers.has(element)) { + let elapsedTime = 0; + let counter = document.createElement("div"); + counter.classList.add("reading-counter"); + counter.style.position = "absolute"; + counter.style.background = "black"; + counter.style.color = "white"; + counter.style.padding = "4px 6px"; + counter.style.borderRadius = "5px"; + counter.style.fontSize = "12px"; + counter.style.zIndex = "9999"; + + document.body.appendChild(counter); + + let interval = setInterval(() => { + elapsedTime += 1000; + + // Vérifie si l'utilisateur est actif et si le temps minimum est atteint + if (userIsActive && elapsedTime >= minReadTime) { + console.log(`[Stats] Élément lu : ${text}`); + readContent.add(text); + sendTextToWorker(text); + stopReadingTimer(element, text); + } + + // Mise à jour de la position du compteur + let rect = element.getBoundingClientRect(); + counter.style.top = `${rect.top + window.scrollY - 20}px`; + counter.style.left = `${rect.left + window.scrollX + rect.width + 10}px`; + counter.innerText = `â³ ${Math.floor(elapsedTime / 1000)}s`; + + }, 1000); + + readingTimers.set(element, { interval, counter, elapsedTime }); + } + } + + /** + * Arrête le chronomètre et supprime l'affichage du temps de lecture + */ + function stopReadingTimer(element, text) { + if (readingTimers.has(element)) { + let { interval, counter } = readingTimers.get(element); + clearInterval(interval); + counter.remove(); + readingTimers.delete(element); + } + } + /** + * Réinitialise tous les chronomètres lors du changement d'onglet + */ + function resetAllTimers() { + for (let [element, { interval, counter }] of readingTimers) { + clearInterval(interval); + counter.remove(); + } + readingTimers.clear(); + } + + /** + * Supprime l'indicateur de lecture + */ + function removeReadingIndicator(element) { + if (readingTimers.has(element)) { + let { counter } = readingTimers.get(element); + counter.remove(); + } + } + /** + * Fonction pour nettoyer le texte + */ function cleanText(text) { - // Supprimer les puces et symboles inutiles text = text.replace(/[\u2022\u00b7•·■◆▪▸▹▶►▻⇨]/g, " "); // Supprime puces et flèches text = text.replace(/[\t\n\r]+/g, " "); // Supprime les sauts de ligne inutiles text = text.replace(/\s{2,}/g, " "); // Remplace plusieurs espaces par un seul - - // Essayer d'extraire le contenu utile des tableaux (ex: "1 | Chat | Animal" → "1 Chat Animal") text = text.replace(/(\||\t)+/g, " "); // Remplace les séparateurs de tableau par un espace text = text.replace(/(\s*-+\s*)+/g, " "); // Supprime les lignes de séparation des tableaux - - // Supprimer les espaces en début et fin de texte text = text.trim(); - return text; } - - - - // STATISTIQUES ------------------------------------------------------------------------------------------ - async function initializeTrackingState() { - const { isTrackingActive: storedState } = await browser.storage.local.get("isTrackingActive"); - isTrackingActive = storedState ?? false; - - log("[Stats] État initial récupéré :", isTrackingActive); - - if (isTrackingActive) { - startTracking(); - } else { - stopTracking(); - } - } + // ───────────────────────────────────────────────────────────────────────────── + // Gestion de l'activation/désactivation des statistiques + // ───────────────────────────────────────────────────────────────────────────── function startTracking() { - log("[Stats] Suivi des statistiques activé."); - addViewportBorder(); - attachScrollListener(); - } + console.log("[Stats] Suivi des statistiques activé."); + addViewportBorder(); + attachScrollListener(); + } function stopTracking() { - log("[Stats] Suivi des statistiques désactivé."); + console.log("[Stats] Suivi des statistiques désactivé."); removeViewportBorder(); detachScrollListener(); } @@ -99,7 +235,7 @@ log("Script stats.js chargé !"); if (!scrollListenerAttached) { window.addEventListener("scroll", trackVisibleContent); scrollListenerAttached = true; - log("[Stats] Écouteur de défilement attaché."); + console.log("[Stats] Écouteur de défilement attaché."); } } @@ -107,11 +243,11 @@ log("Script stats.js chargé !"); if (scrollListenerAttached) { window.removeEventListener("scroll", trackVisibleContent); scrollListenerAttached = false; - log("[Stats] Écouteur de défilement détaché."); + console.log("[Stats] Écouteur de défilement détaché."); } } - // BORDURE ------------------------------------------------------------------------------------------ + // Indice visuel des statistiques actives function injectBorder() { const css = ` #border-svg { @@ -194,33 +330,4 @@ log("Script stats.js chargé !"); svg.remove(); } } - - - - - - - // GESTION DES MESSAGES ------------------------------------------------------------------------------------------ - browser.runtime.onMessage.addListener((message) => { - if (message.command === "pyodide-simplemma-ready") { - log("[Stats] Pyodide et Simplemma prêt, démarrage du scraping : "); - browser.storage.local.get(["extensionActive", "accessToken"].then(({ extensionActive, accessToken }) => { - if (extensionActive && accessToken) { - simplemmaReady = true; - initializeTrackingState(); - } else { - log("[Stats] Extension non activée ou utilisateur déconnecté, désactivation du tracking.") - } - })) - } - - if (message.command === "update-stats") { - if (message.isActive && simplemmaReady) { - startTracking(); - } else { - stopTracking(); - } - } - }); - })(); diff --git a/src/workers/pyodide_worker.js b/src/workers/pyodide_worker.js index 8d54367..ca68c90 100644 --- a/src/workers/pyodide_worker.js +++ b/src/workers/pyodide_worker.js @@ -1,87 +1,190 @@ -importScripts('../utils/logger.js'); +console.log("✅ pyodide_worker.js chargé avec succès !"); // URL de la version Pyodide la plus récente const LATEST_BASE_URL = "https://cdn.jsdelivr.net/pyodide/v0.27.2/full/"; let pyodide = null; -let pyodideLoaded = false; // Variable indiquant si Pyodide est en mémoire -let simplemmaLoaded = false; // Variable indiquant si simplemma est déjà installé +let pyodideLoaded = false; // Indique si Pyodide est en mémoire +let simplemmaLoaded = false; // Indique si simplemma est déjà installé +let storedFrequencies = {}; // Stockage des fréquences accumulées + +let autoAddEnabled = false; // Ajout automatique désactivé par défaut +let isAuthenticated = false; // Non connecté par défaut +let userThreshold = 10; // Seuil par défaut +let trackedLanguages = []; // Aucune langue suivie par défaut +let notifiedWords = {}; // Stockage des mots déjà signalés (pour éviter les doublons) +let stoplistFr = new Set(); // Stockage optimisé de la stoplist // Écouteur des messages reçus du background script self.onmessage = async (event) => { - const data = event.data; - - log("[WebWorker] Message reçu du Background:", data); - - // Initialisation unique de Pyodide puis de Simplemma - if (data.command === "pyodide-simplemma") { - if (pyodideLoaded && simplemmaLoaded) { - log("[Worker] Pyodide et Simplemma déjà chargés."); - self.postMessage({ type: "pyodide-simplemma", status: "already_loaded", message: "Pyodide et Simplemma déjà en mémoire" }); - return; - } + const data = event.data; + console.log("[WebWorker] Message reçu du Background:", data); + if (data.command === "pyodide-simplemma") { + if (pyodideLoaded && simplemmaLoaded) { + console.log("[Worker] Pyodide et Simplemma déjà chargés."); + self.postMessage({ type: "pyodide-simplemma", status: "already_loaded", message: "Pyodide et Simplemma déjà en mémoire" }); + return; + } + try { + if (!pyodideLoaded) { + console.log("[Worker] Chargement de Pyodide..."); try { - if (!pyodideLoaded) { - log("[Worker] Chargement de Pyodide..."); - importScripts(`${LATEST_BASE_URL}pyodide.js`); - pyodide = await loadPyodide({ indexURL: LATEST_BASE_URL }); - await pyodide.loadPackage("lzma"); - await pyodide.loadPackage("micropip"); - pyodideLoaded = true; - log("[Worker] Pyodide chargé avec succès !"); - } - - if (!simplemmaLoaded) { - log("[Worker] Installation de simplemma..."); - await pyodide.runPythonAsync(` - import micropip - import re - - try: - print("Installation de simplemma...") - await micropip.install("simplemma") - print("Installation réussie.") - - import simplemma - print("simplemma importé avec succès.") - - def tokenize(text): - return re.findall(r"\\b\\w+\\b", text.lower()) - - #Phrase de test pour vérifier l'installation de simplemma - print("Phrase de test:") - phrase = "Le chat mange la pomme" - - tokens = tokenize(phrase) - print("Tokens extraits :", tokens) - - lemmatized_tokens = [simplemma.lemmatize(token, lang="fr") for token in tokens] - print("Tokens lemmatisés :", lemmatized_tokens) - - lemmatized_tokens - - except Exception as e: - print("Erreur lors de l'installation ou de l'importation :", e) - e - - `); - simplemmaLoaded = true; - log("[Worker] Simplemma installé avec succès !"); - } - - // Envoyer confirmation au background script - self.postMessage({ type: "pyodide-simplemma", status: "success", message: "Pyodide et Simplemma chargés" }); - - } catch (error) { - console.error("[Worker] Erreur lors du chargement de Pyodide ou Simplemma :", error); - self.postMessage({ type: "pyodide-simplemma", status: "error", message: error.toString() }); + importScripts(`${LATEST_BASE_URL}pyodide.js`); + } catch (err) { + console.error("[Worker] Erreur lors de l'import de pyodide.js :", err); + self.postMessage({ type: "pyodide-simplemma", status: "error", message: err.toString() }); + return; } + pyodide = await loadPyodide({ indexURL: LATEST_BASE_URL }); + await pyodide.loadPackage("lzma"); + await pyodide.loadPackage("micropip"); + pyodideLoaded = true; + console.log("[Worker] Pyodide chargé avec succès !"); + } + + if (!simplemmaLoaded) { + console.log("[Worker] Installation de simplemma..."); + + // On encapsule la logique dans une fonction asynchrone pour faciliter l'usage d'await + await pyodide.runPythonAsync(` +import micropip +import asyncio + +async def main(): + print("Installation de simplemma...") + await micropip.install("simplemma") + print("Installation réussie.") + import simplemma + print("simplemma importé avec succès.") + # Test simple : extraction de tokens et lemmatisation + import re + def tokenize(text): + return re.findall(r"\\b\\w+\\b", text.lower()) + phrase = "Le chat mange la pomme" + tokens = tokenize(phrase) + print("Tokens extraits :", tokens) + lemmatized_tokens = [simplemma.lemmatize(token, lang="fr") for token in tokens] + print("Tokens lemmatisés :", lemmatized_tokens) + return lemmatized_tokens + +await main() + `); + simplemmaLoaded = true; + console.log("[Worker] Simplemma installé avec succès !"); + } + // Envoyer confirmation au background script + self.postMessage({ type: "pyodide-simplemma", status: "success", message: "Pyodide et Simplemma chargés" }); + } catch (error) { + console.error("[Worker] Erreur lors du chargement de Pyodide ou Simplemma :", error); + self.postMessage({ type: "pyodide-simplemma", status: "error", message: error.toString() }); + } + } + + // --- Traitement du texte envoyé par stats.js --- + if (data.command === "process-text") { + if (!pyodideLoaded) { + console.log("[Worker] Pyodide non chargé."); + self.postMessage({ type: "process-text", status: "error", message: "Pyodide pas encore chargé" }); + return; } + console.log("[Worker] Texte reçu pour analyse :", data.text); + try { + const stoplistArray = Array.from(stoplistFr); + console.log("Stoplist utilisée :", stoplistArray); + const result = await pyodide.runPythonAsync(` +import json +import re +import simplemma +from simplemma import langdetect - // Activer/désactiver les statistiques (stats.js) - if (data.command === "toggle-stats") { - log(`[WebWorker] Statistiques ${data.isActive ? "activées" : "désactivées"}`); - return; +def detect_language(text): + lang_scores = simplemma.langdetect(text, lang=("fr", "en", "es", "de", "it", "pt")) + return lang_scores[0][0] if lang_scores else "unk" + +def tokenize(text): + return re.findall(r"\\b[a-zA-ZÀ-ÿ'-]+\\b", text.lower()) + +# Chargement de la stoplist +stoplist = set(json.loads('${JSON.stringify(stoplistArray)}')) + +text = """${data.text.replace(/\"/g, '\\"')}""" +detected_lang = detect_language(text) +if detected_lang == "unk": + detected_lang = "other" + +tokens = tokenize(text) +if detected_lang == "fr": + tokens = [token.lower().strip() for token in tokens if token.lower().strip() not in stoplist] + +lemmatized_tokens = [simplemma.lemmatize(token, lang=detected_lang) for token in tokens] + +freq = {} +for token in lemmatized_tokens: + if token not in stoplist: + freq[token] = freq.get(token, 0) + 1 + +json.dumps({"lang": detected_lang, "frequencies": freq}, ensure_ascii=False) + `); + const parsedResult = JSON.parse(result); + const detectedLang = parsedResult.lang; + if (!storedFrequencies[detectedLang]) { + storedFrequencies[detectedLang] = {}; + } + for (const [word, count] of Object.entries(parsedResult.frequencies)) { + storedFrequencies[detectedLang][word] = (storedFrequencies[detectedLang][word] || 0) + count; + } + self.postMessage({ type: "update-frequencies", frequencies: storedFrequencies }); + if (autoAddEnabled) { + checkThreshold(detectedLang); + } + } catch (error) { + console.error("[Worker] Erreur dans l'analyse du texte :", error); } + } + + if (data.command === "update-preferences") { + userThreshold = data.threshold; + trackedLanguages = data.trackedLanguages; + autoAddEnabled = data.autoAdd; + isAuthenticated = data.isAuthenticated; + console.log("[Worker] Mise à jour des préférences :", { userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated }); + } + + if (data.command === "update-stoplist") { + stoplistFr = new Set(data.stoplist.map(word => word.toLowerCase().trim())); + console.log("[Worker] Stoplist FR mise à jour :", stoplistFr); + } }; + +// --- Vérification du seuil et notification --- +function checkThreshold(lang) { + if (!autoAddEnabled || !isAuthenticated) { + console.log("[Worker] Auto-Add désactivé ou utilisateur non connecté. Aucune vérification de seuil."); + return; + } + if (!trackedLanguages.includes(lang)) { + console.log(`[Worker] La langue ${lang} n'est pas suivie.`); + return; + } + console.log(`[Worker] Vérification des fréquences pour la langue ${lang}...`); + let wordsAboveThreshold = {}; + if (storedFrequencies[lang]) { + const exceededWords = Object.entries(storedFrequencies[lang]) + .filter(([word, count]) => count >= userThreshold && !(notifiedWords[lang] && notifiedWords[lang].includes(word))) + .map(([word]) => word); + if (exceededWords.length > 0) { + if (!notifiedWords[lang]) { + notifiedWords[lang] = []; + } + notifiedWords[lang].push(...exceededWords); + wordsAboveThreshold[lang] = exceededWords; + } + } + if (Object.keys(wordsAboveThreshold).length > 0) { + console.log("[Worker] Nouveaux mots dépassant le seuil :", wordsAboveThreshold); + self.postMessage({ type: "threshold-exceeded", wordsAboveThreshold: wordsAboveThreshold }); + } else { + console.log("[Worker] Aucun nouveau mot n'a dépassé le seuil."); + } +} diff --git a/stoplist_fr.txt b/stoplist_fr.txt new file mode 100644 index 0000000..36fa27b --- /dev/null +++ b/stoplist_fr.txt @@ -0,0 +1,116 @@ +alors +au +aucuns +aussi +autre +avant +avec +avoir +bon +car +ce +cela +ces +ceux +chaque +ci +comme +comment +dans +des +du +dedans +dehors +depuis +devrait +doit +donc +dos +début +elle +elles +en +encore +essai +est +et +eu +fait +faites +fois +font +hors +ici +il +ils +je +juste +la +le +les +leur +là +ma +maintenant +mais +mes +mien +moins +mon +mot +même +ni +nommés +notre +nous +ou +où +par +parce +pas +peut +peu +plupart +pour +pourquoi +quand +que +quel +quelle +quelles +quels +qui +sa +sans +ses +seulement +si +sien +son +sont +sous +soyez +sujet +sur +ta +tandis +tellement +tels +tes +ton +tous +tout +trop +très +tu +voient +vont +votre +vous +vu +ça +étaient +état +étions +été +être \ No newline at end of file -- GitLab