diff --git a/README.md b/README.md index 9d96e439e918111c0cdc797150d730977de55f35..435b323a3866364c6335a1e05904d90bd546fb7d 100644 --- a/README.md +++ b/README.md @@ -24,4 +24,3 @@ Source : [Luciole Vision](https://luciole-vision.com/) **Icônes** Création des icônes d'options sur [icones8](https://icones8.fr/icons) - diff --git a/manifest.json b/manifest.json index 5817a844c640730cf92f218f1a8d4ba04e8302a2..0d06a6f6d9be2633c0feb0fbaea7f7e334fcb466 100644 --- a/manifest.json +++ b/manifest.json @@ -24,7 +24,8 @@ "src/background/background.js", "src/utils/definitions.js", "src/utils/api.js", - "src/context_menu/browser_context_menu.js" + "src/context_menu/browser_context_menu.js", + "src/utils/stats.js" ], "persistent": true }, diff --git a/src/background/background.js b/src/background/background.js index 965e1d09cb9fdb40f83357daddf29c715dfe79c3..98054e48151f5a2f390eb3056285e7c8f257ee46 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -43,18 +43,18 @@ browser.storage.onChanged.addListener((changes) => { }); browser.storage.onChanged.addListener((changes, area) => { - // Vérifie si les changements concernent le stockage local et le token d'accès + // 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; // Récupère la nouvelle valeur du token if (newToken) { - // Vérifie l'état de l'extension dans le stockage local + // 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 + // 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 }); // 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 + // Envoie un message pour mettre à jour l'interface utilisateur browser.runtime.sendMessage({ action: "updateUI", extensionActive: true, @@ -191,7 +191,7 @@ async function saveToken(token) { const { extensionActive } = await browser.storage.local.get("extensionActive"); if (!extensionActive) { await browser.storage.local.set({ extensionActive: true }); // Met à jour l'état de l'extension - updateExtension(); // Met à jour les fonctionnalités de l'extension + updateExtension(); // Met à jour les fonctionnalités de l'extension browser.runtime.sendMessage({ action: "updateUI", extensionActive: true, @@ -508,10 +508,6 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { const { isActive } = message; // Met à jour l'état du suivi des statistiques dans le stockage local await browser.storage.local.set({ isTrackingActive: isActive }); - // Si les statistiques sont désactivées, désactive également l'ajout automatique - if (!isActive) { - await browser.storage.local.set({ autoAdd: false }); - } // Vérifie et met à jour l'état du suivi des statistiques checkAndUpdateTracking(); } @@ -525,6 +521,113 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { return true; }); +// ───────────────────────────────────────────────────────────────────────────── +// Fonction : Chargement des lexiques personnels dans le local storage +// ───────────────────────────────────────────────────────────────────────────── +async function saveUserLexicons() { + const { accessToken } = await browser.storage.local.get("accessToken"); + if (!accessToken) { + console.warn("Aucun token disponible, impossible de récupérer les lexiques."); + return; + } + log("Récupération des lexiques..."); + const lexicons = await getLexicons(accessToken); + const userLexicons = lexicons.filter(lexicon => lexicon.category === "User"); + if (userLexicons.length > 0) { + await browser.storage.local.set({ lexicons: userLexicons }); + log("Lexiques enregistrés dans le local storage :", userLexicons); + } else { + log("Aucun lexique utilisateur trouvé."); + } + +} + + +// ───────────────────────────────────────────────────────────────────────────── +// Envoi des données au WebWorker : lexiques personnels et token +// ───────────────────────────────────────────────────────────────────────────── + +// Charger et envoyer les lexiques au worker à la connexion + les stoplists associées +browser.storage.onChanged.addListener(async (changes, area) => { + if (area === "local" && changes.accessToken) { + log("Token mis à jour, récupération des lexiques..."); + const userLexicons = await saveUserLexicons(); // Récupérer les lexiques + sendLexiconsToWorker(userLexicons); // Envoyer les lexiques au Worker après une connexion + sendAuthTokenToWorker(); + } +}); + +// Envoyer les lexiques et stoplists au Worker +async function sendLexiconsToWorker(userLexicons = null) { + if (!userLexicons) { + const storedData = await browser.storage.local.get("lexicons"); + userLexicons = storedData.lexicons || []; + } + if (!Array.isArray(userLexicons) || userLexicons.length === 0) { + console.warn("[Background] Aucun lexique à envoyer au Worker."); + return; + } + log("[Background] Envoi des lexiques au Worker..."); + if (worker) { + worker.postMessage({ + command: "update-lexicons", + lexicons: JSON.stringify(userLexicons) + }); + + // Charger et envoyer uniquement les stoplists des langues des lexiques utilisateur + const languages = [...new Set(userLexicons.map(lexicon => lexicon.language))]; + log("[Background] Langues détectées :", languages); + loadStoplistsForLanguages(languages); + log("Lexiques envoyés au WebWorker !"); + } +} + + +// Charger et envoyer le token au worker à la connexion +async function sendAuthTokenToWorker() { + if (!worker) { + console.warn("Worker non initialisé. Impossible d'envoyer le token."); + return; + } + const { accessToken } = await browser.storage.local.get("accessToken"); + if (!accessToken) { + console.warn("Aucun token disponible. Le worker ne pourra pas interagir avec l’API."); + return; + } + log("Envoi du token au Worker..."); + worker.postMessage({ command: "update-auth-token", accessToken }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Stoplists : Chargement et envoi au Worker +// ───────────────────────────────────────────────────────────────────────────── +async function loadStoplistsForLanguages(languages) { + const stoplists = {}; + // Charger toutes les stoplists en parallèle + await Promise.all( + languages.map(async (lang) => { + const stoplistPath = `src/stoplists/stoplist_${lang}.txt`; + try { + 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`); + } catch (error) { + console.warn(`[Background] âš Stoplist introuvable pour '${lang}', aucun filtrage ne sera appliqué.`); + } + }) + ); + sendStoplistsToWorker(stoplists); +} + +function sendStoplistsToWorker(stoplists) { + log("[Background] Envoi des stoplists au Worker..."); + worker.postMessage({ command: "update-stoplist", stoplists }); +} + +// Charger les stoplists uniquement quand les lexiques sont disponibles +browser.runtime.onStartup.addListener(sendLexiconsToWorker); +browser.runtime.onInstalled.addListener(sendLexiconsToWorker); // ───────────────────────────────────────────────────────────────────────────── // Chargement et sauvegarde des fréquences stockées @@ -608,6 +711,16 @@ browser.storage.onChanged.addListener(async (changes, area) => { autoAdd: autoAdd || false }); } + + //É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}`); + + if (worker) { + worker.postMessage({ command: "update-include-stopwords", includeStopwords }); + } + } }); // ───────────────────────────────────────────────────────────────────────────── @@ -649,38 +762,6 @@ browser.runtime.onConnect.addListener((port) => { } }); -// ───────────────────────────────────────────────────────────────────────────── -// Stoplists : Chargement et envoi au Worker -// ───────────────────────────────────────────────────────────────────────────── -let stoplistFr = []; - -/** - * Charge la stoplist depuis un fichier et l'envoie au Worker. - * @returns {void} - */ -function loadStoplist() { - fetch(browser.runtime.getURL("stoplist_fr.txt")) - .then(response => response.text()) - .then(text => { - stoplistFr = text.split("\n").map(word => word.trim()); - log("[Background] Stoplist chargée :", stoplistFr); - sendStoplistToWorker(); - }) - .catch(error => log("[Background] Erreur lors du chargement de la stoplist :", error)); -} - -/** - * Envoie la stoplist au Worker. - * @returns {void} - */ -function sendStoplistToWorker() { - log("[Background] Envoi de la stoplist au Worker..."); - worker.postMessage({ command: "update-stoplist", stoplist: stoplistFr }); -} - -browser.runtime.onStartup.addListener(loadStoplist); -browser.runtime.onInstalled.addListener(loadStoplist); - // ───────────────────────────────────────────────────────────────────────────── // Surlignage // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/context_menu/custom_context_menu.js b/src/context_menu/custom_context_menu.js index 85cad9f0cf8c1fe8d1377e45f981ee8ce9489961..50d28753ba96da1c7e8cb2cf584991c4dad10a2d 100644 --- a/src/context_menu/custom_context_menu.js +++ b/src/context_menu/custom_context_menu.js @@ -384,11 +384,23 @@ function hideLexiconPicker() { // ───────────────────────────────────────────────────────────────────────────── // â–Œ Écouteurs d'événements // ───────────────────────────────────────────────────────────────────────────── -// Écoute globale pour la sélection de texte +// Écouteur global pour la sélection de texte et la gestion des clics 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 + // Vérifie si le clic est à l'intérieur du menu contextuel, si oui, ne fait rien if (event.target.closest("#customContextMenu")) return; + // 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) { @@ -426,31 +438,4 @@ browser.storage.onChanged.addListener((changes) => { log("Token modifié dans le stockage, mise à jour du menu contextuel."); loadAuthToken().then(updateMenuVisibility); // Recharge le token et met à jour la visibilité } -}); - -// Écouteur pour masquer le menu contextuel et le sélecteur -document.addEventListener("mouseup", (event) => { - const customContextMenu = document.getElementById(CUSTOM_CONTEXT_MENU); // Récupère le menu contextuel - const picker = document.getElementById("lexiconPicker"); // Récupère le sélecteur de lexique - - // Masque le menu contextuel si le clic est en dehors de celui-ci - if (customContextMenu && !customContextMenu.contains(event.target)) { - hideCustomContextMenu(); - } - // Masque le sélecteur si le clic est en dehors de celui-ci - if (picker && !picker.contains(event.target)) { - hideLexiconPicker(); - } - - // Récupère le texte sélectionné - const selectedText = window.getSelection().toString().trim(); - if (selectedText) { - log("Texte sélectionné :", selectedText); - showCustomContextMenu(event, selectedText); - // Envoie un message au runtime avec le texte sélectionné - browser.runtime.sendMessage({ - action: "mot_selectionne", - selectedText, - }); - } }); \ No newline at end of file diff --git a/src/css/sidebar.css b/src/css/sidebar.css index 76e6dc47dca1b3ee394a2140d59132ae7a59df7e..4c445fbda34d04fe6fae713d999f54bd9ddf10e2 100644 --- a/src/css/sidebar.css +++ b/src/css/sidebar.css @@ -231,11 +231,17 @@ button.lexique-highlight-toggle:hover .tooltip { margin-bottom: 10px; } .lexicon-header { - font-weight: bold; - cursor: pointer; + position: relative; padding: 5px; + padding-right: 20px; background-color: #8d5c70; border-radius: 5px; + cursor: pointer; +} +.lexicon-header span { + display: block; + font-size: 13px; + line-height: 1.2; text-align: center; } .lexicon-header:hover { @@ -244,6 +250,7 @@ button.lexique-highlight-toggle:hover .tooltip { } .lexicon-content { margin-top: 5px; + width: auto; } .lexicon-option { margin-right: -10px; @@ -256,6 +263,17 @@ button.lexique-highlight-toggle:hover .tooltip { #mesLexiquesContainer h4 { margin-bottom: 5px; } +#wiktionnaireContainer h4 { + margin-bottom: 2px !important; +} +.balex-icon { + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + cursor: zoom-in; + flex-shrink: 0; +} /* Cases à cocher pour les lexiques */ .lexique-checkbox { @@ -336,6 +354,10 @@ button.lexique-highlight-toggle:hover .tooltip { } /* Section Définitions */ +.definition-item { + font-size: 13px; + padding-left: 10px; +} #definitionContainer { background-color: #444; padding: 10px; @@ -344,7 +366,7 @@ button.lexique-highlight-toggle:hover .tooltip { } #definitionsList { list-style: none; - padding: 0; + padding: 2px; } #definitionsList li { margin-bottom: 10px; @@ -358,6 +380,14 @@ button.lexique-highlight-toggle:hover .tooltip { color: red !important; font-weight: bold; } +#wiktionnaireList .definition-item { + font-size: 13px; + padding-left: 2px; + padding-top: 0; +} +#wiktionnaireList { + padding-left: 2px; +} /* Modal de définition */ .modal-overlay { diff --git a/src/plugin/displayStats.js b/src/plugin/displayStats.js new file mode 100644 index 0000000000000000000000000000000000000000..cc949856424e2b7c38170150ab2076b9428d1749 --- /dev/null +++ b/src/plugin/displayStats.js @@ -0,0 +1,145 @@ +// stats.js +document.addEventListener("DOMContentLoaded", async () => { + // Fonction pour recalculer et stocker le résumé des statistiques + async function updateStatsSummary() { + console.log("[Stats Page] Mise à jour du résumé des statistiques..."); + + const { + lemmaFrequencies = {}, + trackedLanguages = [], + wordsAdded = {} + } = await browser.storage.local.get([ + "lemmaFrequencies", + "trackedLanguages", + "wordsAdded" + ]); + + let totalWords = 0, totalUniqueWords = 0; + const languages = {}; + + for (const [lang, words] of Object.entries(lemmaFrequencies)) { + if (!trackedLanguages.length || trackedLanguages.includes(lang)) { + let wordCount = 0; + let uniqueWordCount = 0; + for (const count of Object.values(words)) { + wordCount += count; + uniqueWordCount++; + } + languages[lang] = { totalWords: wordCount, uniqueWords: uniqueWordCount }; + totalWords += wordCount; + totalUniqueWords += uniqueWordCount; + } + } + + const summary = { + totalWords, + totalUniqueWords, + languages, + wordsAdded + }; + + console.log("[Stats Page] Résumé des statistiques mis à jour:", summary); + await browser.storage.local.set({ statsSummary: summary }); + return { summary, lemmaFrequencies, trackedLanguages }; + } + + // Générer le HTML du résumé global + function generateSummaryHTML(summary) { + if (summary.totalWords === 0) { + return `<h2>Résumé des statistiques</h2> + <p>Aucune statistique disponible pour le moment.</p>`; + } + return ` + <h2>Résumé des statistiques</h2> + <p>Date : ${new Date().toLocaleDateString()}</p> + <p>Total de mots analysés : ${summary.totalWords}</p> + <p>Total de mots uniques : ${summary.totalUniqueWords}</p> + `; + } + + // Générer le contrôle de tri (placé juste au-dessus des tableaux) + function generateSortControlHTML(currentSortOrder) { + return ` + <div id="sort-container"> + <label for="sort-order">Trier par fréquence : </label> + <select id="sort-order"> + <option value="desc" ${currentSortOrder === "desc" ? "selected" : ""}>Décroissante</option> + <option value="asc" ${currentSortOrder === "asc" ? "selected" : ""}>Croissante</option> + </select> + </div> + `; + } + + // Générer le HTML détaillé pour chaque langue avec le total par langue + function generateDetailedFrequenciesHTML(lemmaFrequencies, trackedLanguages, sortOrder) { + let detailsHTML = `<div id="detailed-frequencies">`; + let hasData = false; + + for (const [lang, words] of Object.entries(lemmaFrequencies)) { + if (!trackedLanguages.length || trackedLanguages.includes(lang)) { + let wordEntries = Object.entries(words); + if (wordEntries.length === 0) { + detailsHTML += `<h4>${lang.toUpperCase()}</h4> + <p>Aucune donnée pour cette langue.</p>`; + } else { + hasData = true; + // Calculer le total pour cette langue + const totalForLang = wordEntries.reduce((acc, curr) => acc + curr[1], 0); + // Trier les mots par fréquence + wordEntries.sort((a, b) => sortOrder === "asc" ? a[1] - b[1] : b[1] - a[1]); + detailsHTML += `<h4>${lang.toUpperCase()} (Total: ${totalForLang} mots)</h4>`; + detailsHTML += `<div class="table-container"> + <table> + <tr> + <th>Mot</th> + <th>Fréquence</th> + </tr>`; + for (const [word, freq] of wordEntries) { + detailsHTML += `<tr> + <td>${word}</td> + <td>${freq}</td> + </tr>`; + } + // // Ligne de récapitulation pour le total de mots de la langue + // detailsHTML += `<tr> + // <td colspan="2" style="text-align:right; font-weight:bold;"> + // AJOUT: Total + // : ${totalForLang} + // </td> + // </tr>`; + detailsHTML += `</table> + </div>`; + } + } + } + + if (!hasData) { + detailsHTML += `<p>Aucune donnée de fréquence à afficher.</p>`; + } + detailsHTML += `</div>`; + return detailsHTML; + } + + const container = document.getElementById("stats-container"); + let currentSortOrder = "desc"; + + const { summary, lemmaFrequencies, trackedLanguages } = await updateStatsSummary(); + container.innerHTML = generateSummaryHTML(summary) + + generateSortControlHTML(currentSortOrder) + + generateDetailedFrequenciesHTML(lemmaFrequencies, trackedLanguages, currentSortOrder); + + // Écouteur pour mettre à jour l'affichage lorsque l'utilisateur change l'ordre de tri + document.getElementById("sort-order").addEventListener("change", async (e) => { + currentSortOrder = e.target.value; + const data = await updateStatsSummary(); + const detailedHTML = generateDetailedFrequenciesHTML(data.lemmaFrequencies, data.trackedLanguages, currentSortOrder); + const detailedContainer = document.getElementById("detailed-frequencies"); + if (detailedContainer) { + detailedContainer.outerHTML = detailedHTML; + } else { + container.innerHTML = generateSummaryHTML(data.summary) + + generateSortControlHTML(currentSortOrder) + + detailedHTML; + } + }); +}); diff --git a/src/plugin/plugin.html b/src/plugin/plugin.html index 98cb1c18abfeeac6e7fa50a33c7fb66b1c1dc9a3..ca17e78d592e801a20fe1145af7ac348e654abf3 100644 --- a/src/plugin/plugin.html +++ b/src/plugin/plugin.html @@ -9,7 +9,7 @@ </head> <body> <div id="extension-name">Extension BaLex</div> - + <!-- Section de connexion --> <div id="auth-section"> <button id="auth-button"> @@ -26,9 +26,9 @@ <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 --> + <!-- 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"> @@ -38,30 +38,30 @@ <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 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> + <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 --> diff --git a/src/plugin/plugin.js b/src/plugin/plugin.js index f95ae3890bded642a9276209a4c33485b8ae581b..91d6795a4b750df0fe112b2890cca74412785c17 100644 --- a/src/plugin/plugin.js +++ b/src/plugin/plugin.js @@ -11,25 +11,245 @@ async function getAccessToken() { return accessToken; } -// ───────────────────────────────────────────────────────────────────────────── -// â–Œ Gestion de la connexion -// ───────────────────────────────────────────────────────────────────────────── -/** - * Met à jour le bouton de connexion en fonction de l'état de connexion. - */ +// ========= +// 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 }); + // Ouvrir la page des statistiques si désactivé + if (!newState) { + openStatsPage(); + } + // 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() { - // Récupération du token d'accès const accessToken = await getAccessToken(); - // Sélection du bouton de connexion + //Vérification du bouton de connexion const button = document.getElementById("auth-button"); - - // Vérification si le bouton existe if (!button) { - log("Le bouton de connexion n'a pas été trouvé."); + console.error("⌠Le bouton de connexion n'a pas été trouvé."); return; } - - // Mise à jour du bouton en fonction de l'état de connexion if (accessToken) { button.textContent = "Se déconnecter"; button.style.position = "relative"; @@ -65,27 +285,33 @@ async function updateLanguageSelection() { const languageSelection = document.getElementById("language-selection"); languageSelection.innerHTML = "<p id='loading-languages' style='color: gray;'>Chargement...</p>"; - const accessToken = await getAccessToken(); - // Vérification si l'utilisateur est connecté - if (!accessToken) { - languageSelection.innerHTML = "<p style='color: red;'>Veuillez vous connecter.</p>"; + 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; } - // Récupération des lexiques et langues de l'utilisateur - const lexicons = await getLexicons(accessToken); + // Extraire les langues uniques const userLanguages = [...new Set(lexicons.map(lex => lex.language))]; - // Récupération des langues suivies depuis le stockage local - const { trackedLanguages } = await browser.storage.local.get("trackedLanguages") || { trackedLanguages: [] }; + // 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 = ""; - if (userLanguages.length === 0) { - languageSelection.innerHTML = "<p style='color: red;'>Aucun lexique personnel trouvé.</p>"; // Message d'erreur si aucun lexique - return; - } - - // Création des boutons pour chaque langue userLanguages.forEach(lang => { const langButton = document.createElement("div"); langButton.classList.add("lang-option"); @@ -105,54 +331,22 @@ async function updateLanguageSelection() { log("Sélection des langues mise à jour avec :", userLanguages); } -// ───────────────────────────────────────────────────────────────────────────── -// â–Œ Gestion principale de l'extension -// ───────────────────────────────────────────────────────────────────────────── -/** - * Met à jour l'extension. - */ -async function updateExtension() { - // Récupération de tous les états nécessaires - const accessToken = await getAccessToken(); - const { - extensionActive, - isTrackingActive, - autoAdd, - threshold, - pyodideSimplemmaReady - } = await browser.storage.local.get([ - "extensionActive", - "isTrackingActive", - "autoAdd", - "threshold", - "pyodideSimplemmaReady" - ]) || { - extensionActive: false, - isTrackingActive: false, - autoAdd: false, - threshold: 10, - pyodideSimplemmaReady: false - }; - - // Vérification si l'utilisateur est connecté - const isLoggedIn = !!accessToken; - - // Mise à jour du bouton d'activation de l'extension +// ========= +// Gestion bouton d'activation de l'extension +async function updateToggleExtensionButton(isLoggedIn, extensionActive, autoAdd, isTrackingActive, pyodideSimplemmaReady, includeStopwords) { const toggleExtensionBtn = document.getElementById("toggleExtensionBtn"); + if (toggleExtensionBtn) { - // Mise à jour du texte du bouton en fonction de l'état de l'extension toggleExtensionBtn.textContent = extensionActive ? "Désactiver l'analyse" : "Activer l'analyse"; toggleExtensionBtn.style.pointerEvents = isLoggedIn ? "auto" : "none"; toggleExtensionBtn.disabled = !isLoggedIn; toggleExtensionBtn.style.position = "relative"; toggleExtensionBtn.className = "tooltip-container"; - // Suppression de l'ancien tooltip s'il existe const existingTooltipExt = toggleExtensionBtn.querySelector('.tooltip'); if (existingTooltipExt) { existingTooltipExt.remove(); } - // Création d'un nouveau tooltip const tooltipExt = document.createElement("span"); tooltipExt.className = "tooltip"; tooltipExt.style.opacity = "1 !important"; @@ -166,16 +360,12 @@ async function updateExtension() { tooltipExt.style.display = "none"; } toggleExtensionBtn.appendChild(tooltipExt); - - toggleExtensionBtn.onclick = handleToggleExtension; } // Mise à jour des options de statistiques const statsOptions = document.getElementById("stats-options"); const toggleStatsBtn = document.getElementById("toggleStatsBtn"); const openStats = document.getElementById("open-stats"); - - // Affichage ou masquage des options de statistiques if (statsOptions) { statsOptions.style.display = (isLoggedIn && extensionActive) ? "block" : "none"; } @@ -188,12 +378,8 @@ async function updateExtension() { toggleStatsBtn.disabled = !isEnabled; toggleStatsBtn.style.position = "relative"; toggleStatsBtn.className = "tooltip-container"; - - // Suppression de l'ancien tooltip s'il existe const existingTooltipStats = toggleStatsBtn.querySelector('.tooltip'); if (existingTooltipStats) { existingTooltipStats.remove(); } - - // Création d'un nouveau tooltip pour les statistiques const tooltipStats = document.createElement("span"); tooltipStats.className = "tooltip"; tooltipStats.style.opacity = "1 !important"; @@ -208,8 +394,6 @@ async function updateExtension() { } toggleStatsBtn.appendChild(tooltipStats); } - - // Affichage ou masquage du bouton pour ouvrir les statistiques if (openStats) { openStats.style.display = (isLoggedIn && extensionActive && isTrackingActive) ? "block" : "none"; } @@ -265,170 +449,40 @@ async function updateExtension() { extensionActive, isTrackingActive, autoAdd, - pyodideSimplemmaReady + pyodideSimplemmaReady, + includeStopwords }); } -/** - * Gère le clic sur le bouton d'activation de l'extension (analyse). - */ -async function handleToggleExtension() { - const accessToken = await getAccessToken(); - if (!accessToken) return; - await proceedToggleExtension(); - await updateConnectionButton(); -} - -/** - * Effectue le changement d'état de l'extension (analyse). - */ -async function proceedToggleExtension() { - // Récupération de l'état actuel de l'extension et du suivi - const { extensionActive, isTrackingActive } = await browser.storage.local.get({ - extensionActive: false, // Valeur par défaut si non trouvée - isTrackingActive: false // Valeur par défaut si non trouvée - }); - log("État actuel de extensionActive avant changement :", extensionActive); - - const newState = !extensionActive; - // Mise à jour de l'état de l'extension dans le stockage local - await browser.storage.local.set({ extensionActive: newState }); - log("Nouvel état de extensionActive :", newState); - - // Envoi d'un message pour mettre à jour l'interface utilisateur - browser.runtime.sendMessage({ action: "updateUI", extensionActive: newState }); - - // Si l'extension est désactivée - if (!newState) { - // Désactivation du suivi - await browser.storage.local.set({ isTrackingActive: false }); - // Ouverture de la page des statistiques si le suivi était actif - if (isTrackingActive) window.open("stats.html", "_blank"); - // Envoi d'un message pour fermer les blocs de la barre latérale - browser.runtime.sendMessage({ action: "closeSidebarBlocks" }); +//Activer/désactiver les stoplists +async function updateStopwordsOption(includeStopwords) { + const stopwordsCheckbox = document.getElementById("include-stopwords"); + if (stopwordsCheckbox) { + stopwordsCheckbox.checked = includeStopwords; } - - // Envoi d'un message pour changer l'état de l'extension - browser.runtime.sendMessage({ action: "toggleExtension", isActive: newState }); - // Mise à jour de l'extension après le changement d'état - await updateExtension(); } -// ───────────────────────────────────────────────────────────────────────────── -// â–Œ Écouteurs d'événements -// ───────────────────────────────────────────────────────────────────────────── -/** - * Met à jour le bouton de connexion et l'extension lorsque le DOM est chargé. - */ -document.addEventListener("DOMContentLoaded", async () => { - await updateConnectionButton(); - await updateExtension(); -}); -/** - * Gère le clic sur le bouton des statistiques. - */ -document.getElementById("toggleStatsBtn")?.addEventListener("click", async () => { - const accessToken = await getAccessToken(); - if (!accessToken) return; - // Récupération de l'état actuel du suivi - const current = await browser.storage.local.get("isTrackingActive"); - const newState = !current.isTrackingActive; - await browser.storage.local.set({ isTrackingActive: newState }); - - // Si le suivi est désactivé, désactiver également l'ajout automatique - if (!newState) { - await browser.storage.local.set({ autoAdd: false }); - } - - // Envoi d'un message pour changer l'état des statistiques - browser.runtime.sendMessage({ command: "toggle-stats", isActive: newState }); - // Si le suivi est activé, envoyer un message pour pyodide-simplemma - if (newState) { - browser.runtime.sendMessage({ command: "pyodide-simplemma" }); - } - - // Mise à jour de l'affichage des lexiques - if (isUpdatingLexicons) return; - isUpdatingLexicons = true; - await updateLexiconsDisplay(); - isUpdatingLexicons = false; - await updateExtension(); -}); - -/** - * Gère le changement de l'ajout automatique. - */ -document.getElementById("auto-add")?.addEventListener("change", async () => { - const isChecked = document.getElementById("auto-add").checked; - document.getElementById("auto-add-options").classList.toggle("hidden", !isChecked); - document.getElementById("save-options").classList.toggle("hidden", !isChecked); - - // Si l'ajout automatique est désactivé, mettre à jour l'état - if (!isChecked) { - await browser.storage.local.set({ autoAdd: false }); - await updateExtension(); - } -}); - -/** - * Sauvegarde des options. - */ -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); - - const errorMessage = document.getElementById("error-message"); - // Vérification si l'ajout automatique est activé sans langues sélectionnées - if (autoAdd && selectedLanguages.length === 0) { - errorMessage?.classList.remove("hidden"); // Affichage du message d'erreur - return; - } - errorMessage?.classList.add("hidden"); - - // Sauvegarde des options dans le stockage local - await browser.storage.local.set({ - autoAdd, - threshold, - trackedLanguages: selectedLanguages - }); - - await updateExtension(); - log("Options sauvegardées et interface mise à jour"); -}); - -/** - * Ouvre la page des statistiques. - */ -document.getElementById("open-stats")?.addEventListener("click", () => { - window.open("stats.html", "_blank"); -}); - -/** - * Affiche le message d'information sur les langues suivies. - */ -document.addEventListener("DOMContentLoaded", () => { - const optionRows = document.querySelectorAll('.option-row label'); - optionRows.forEach(label => { - if (label.textContent.trim() === "Langues suivies") { - label.classList.add("tooltip-container"); - - const tooltip = document.createElement("span"); - tooltip.className = "tooltip tooltip-langues-suivies"; - tooltip.textContent = "Choisissez une ou plusieurs langues parmi celles de vos lexiques personnels"; - label.appendChild(tooltip); - } - }); +// ✅ 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 }); -// ───────────────────────────────────────────────────────────────────────────── -// â–Œ Gestion des messages -// ───────────────────────────────────────────────────────────────────────────── +// !! 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 plugin.js :", message); + log("📩 Message reçu dans popup.js :", message); if (message.action === "updateUI") { await updateExtension(); } else if (message.action === "notify") { @@ -474,6 +528,21 @@ function handleWorkerMessage(event) { message: data.message }); return; - } + }} + + +// ========================== +// Affichage des statistiques +// ========================== + +// Bouton pour ouvrir la page des statistiques +document.getElementById("open-stats")?.addEventListener("click", async () => { + openStatsPage(); +}); + +function openStatsPage() { + browser.tabs.create({ + url: browser.runtime.getURL("stats.html") + }); } diff --git a/src/plugin/stats.html b/src/plugin/stats.html new file mode 100644 index 0000000000000000000000000000000000000000..9cbe36c42ed0932f25ce5b80748ab5014ad2cd07 --- /dev/null +++ b/src/plugin/stats.html @@ -0,0 +1,56 @@ +<!-- stats.html --> +<!DOCTYPE html> +<html lang="fr"> +<head> + <meta charset="UTF-8"> + <title>Résumé des statistiques</title> + <script src="displayStats.js"></script> + <style> + /* Style global pour une présentation compacte et lisible */ + body { + font-family: Arial, sans-serif; + margin: 20px; + padding: 0; + } + #stats-container { + max-width: 600px; + margin: 0 auto; + } + /* Contrôle de tri centré et espacé */ + #sort-container { + margin: 20px 0; + text-align: center; + } + /* Tableaux compacts */ + table { + width: 100%; + border-collapse: collapse; + font-size: 0.85em; + margin-bottom: 15px; + } + table, th, td { + border: 1px solid #ddd; + } + th, td { + padding: 6px 8px; + text-align: left; + } + th { + background-color: #ae8bac; + } + h4 { + margin-bottom: 5px; + } + /* Optionnel : encadrer les tableaux dans un conteneur avec défilement si trop de lignes */ + .table-container { + max-height: 300px; + overflow-y: auto; + } + </style> +</head> +<body> + <div id="stats-container"> + <p>Chargement des statistiques…</p> + </div> +</body> +</html> diff --git a/src/sidebar/sidebar.html b/src/sidebar/sidebar.html index fce4745e7dfc2f650699a92c3bf1825bb17fd33e..62bba510c550df4a77156adbc34494cb79e8f338 100644 --- a/src/sidebar/sidebar.html +++ b/src/sidebar/sidebar.html @@ -32,7 +32,7 @@ <button class="toggle-btn">+</button> <!-- Bouton pour afficher/masquer le bloc --> </div> <div id="menuContent" class="block-content hidden"> - <div id="lexiques">Chargement...</div> + <div id="lexiques">Chargement...</div> </div> </div> @@ -42,8 +42,8 @@ <h3>Mot sélectionné</h3> <button class="toggle-btn"></button> </div> - <div id="etatContent" class="block-content hidden"> - <p id="motSelectionne">Aucun mot sélectionné</p> + <div id="etatContent" class="block-content hidden"> + <p id="motSelectionne">Aucun mot sélectionné</p> <p id="lexiconResult"></p> <div id="add-to-lexiques" style="display: none;"> <button id="add-word-button">Ajouter le mot sélectionné</button> @@ -63,7 +63,7 @@ <!-- Définitions des lexiques de l'utilisateur --> <div id="mesLexiquesContainer"> <h4>📚 Mes lexiques</h4> - <label style="font-size: small;"> + <label style="font-size: 12px;"> <input type="checkbox" id="toggle-definitions"> Afficher toutes les définitions </label> diff --git a/src/stoplists/stoplist_en.txt b/src/stoplists/stoplist_en.txt new file mode 100644 index 0000000000000000000000000000000000000000..b6fc4c620b67d95f953a5c1c1230aaab5db5a1b0 --- /dev/null +++ b/src/stoplists/stoplist_en.txt @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/src/stoplists/stoplist_fr.txt b/src/stoplists/stoplist_fr.txt new file mode 100644 index 0000000000000000000000000000000000000000..3a98580e221f3b4416ab3bfe9863941ae1951e34 --- /dev/null +++ b/src/stoplists/stoplist_fr.txt @@ -0,0 +1,117 @@ +chat +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 diff --git a/src/utils/api.js b/src/utils/api.js index efcd5749ab1841bcde21902e6142c221a7067bfa..bf833a23530ef542724ba837c32b667943f39916 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -96,45 +96,45 @@ async function getAllLexiconWords(authToken) { try { // 1) Récupération de la liste des lexiques const lexicons = await getLexicons(authToken); - - // Vérification si la liste des lexiques est valide - if (!Array.isArray(lexicons) || lexicons.length === 0) { - console.warn("âš ï¸ Aucun lexique retourné par l'API pour ces paramètres."); - return {}; - } - // 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) { - // 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; + 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 {}; } - // 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; } - - 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 // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/utils/definitions.js b/src/utils/definitions.js index 881ed5152af74d776f828132c832a4a737057147..d8ff193d60da2a0cbceb271731b6b58b08dfd646 100644 --- a/src/utils/definitions.js +++ b/src/utils/definitions.js @@ -1,5 +1,5 @@ // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Récupération des définitions (lexiques + Wiktionnaire) +// â–Œ Fonctions pour récupérer/afficher les définitions // ───────────────────────────────────────────────────────────────────────────── window.lexiconMap = new Map(); @@ -27,7 +27,7 @@ async function fetchLexiconDefinitions(word) { throw new Error(`⌠Erreur API lors de la récupération des lexiques: ${lexResponse.statusText}`); } const userLexicons = await lexResponse.json(); - log("ðŸ—‚ï¸ Lexiques de l'utilisateur :", userLexicons); + log("Lexiques de l'utilisateur :", userLexicons); if (!Array.isArray(userLexicons) || userLexicons.length === 0) { console.warn("âš ï¸ Aucun lexique trouvé pour cet utilisateur."); @@ -72,7 +72,7 @@ async function fetchLexiconDefinitions(word) { const results = await Promise.all(definitionsPromises); - // 3) Parcourir les résultats et extraire les définitions + // 3) Parcourir les résultats et extraire les définitions + prononciations let allDefinitions = []; results.forEach(result => { log(`Pour le lexique ${result.lexiconId}, entrées filtrées :`, result.entries); @@ -91,8 +91,17 @@ async function fetchLexiconDefinitions(word) { } } + const balexId = entry.balex_id || null; + items.forEach(item => { const definitionsArray = item.Sense?.Definitions; + const pronunciationsArray = item.Sense?.Pronunciations; + + let pronunciations = []; + if (Array.isArray(pronunciationsArray)) { + pronunciations = pronunciationsArray.map(p => p.Transcription).filter(p => p); + } + if (!Array.isArray(definitionsArray)) return; definitionsArray.forEach(defObj => { @@ -100,6 +109,8 @@ async function fetchLexiconDefinitions(word) { allDefinitions.push({ source: sourceName, text: defObj.Def, + balex_id: balexId, + pronunciations: pronunciations, lexiconId: lexiconId }); } @@ -107,31 +118,36 @@ async function fetchLexiconDefinitions(word) { }); }); }); - - log("Résultat final filtré :", allDefinitions); - return allDefinitions; - } catch (error) { - log("Erreur générale lors de la récupération des définitions :", error); - return []; - } -} + log("Résultats filtré depuis les lexiques utilisateurs :", allDefinitions); + return allDefinitions; + } catch (error) { + log("⌠Erreur générale lors de la récupération des définitions :", error); + return []; + } + } /** - * Récupère la définition d'un mot depuis le Wiktionnaire (fr). - * Retourne un tableau d'objets : [{ source: 'Wiktionnaire', text: '...' }] - * @param {string} word - Le mot dont on veut la définition. - * @returns {Promise<object[]>} - Un tableau d'objets contenant la définition. - */ +* Récupère la définition d'un mot depuis le Wiktionnaire (fr). +* Retourne un tableau d'objets : [{ source: 'Wiktionnaire', text: '...' }] +*/ async function fetchWiktionaryDefinition(word) { try { const result = await browser.storage.local.get("accessToken"); authToken = result.accessToken; + // 🔹 Initialisation d'une structure vide pour éviter les erreurs + let formattedData = { + definitions: [], + pronunciations: [], + definitionsByPOS: {} // Pour stocker les définitions triées par POS + }; + if (!authToken) { log(`🔠Requête Wiktionnaire pour "${word}"...`); if (!word || word.trim() === "") { throw new Error("âš ï¸ Mot vide, impossible d'envoyer la requête."); } + const wiktionaryURL = `https://fr.wiktionary.org/w/api.php?action=query&format=json&origin=*&prop=extracts&explaintext=true&redirects=1&titles=${encodeURIComponent(word)}`; const response = await fetch(wiktionaryURL); if (!response.ok) { @@ -142,17 +158,19 @@ async function fetchWiktionaryDefinition(word) { 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); + formattedData.definitions = page && page.extract + ? [page.extract.trim()] + : ["Aucune définition trouvée sur le Wiktionnaire."]; + + log("Définition Wiktionnaire extraite :", formattedData.definitions); return [ { source: "Wiktionnaire", - text: definitionText + text: formattedData.definitions.join(" | "), + pronunciations: formattedData.pronunciations, + definitionsByPOS: formattedData.definitionsByPOS } ]; } else { @@ -163,32 +181,47 @@ async function fetchWiktionaryDefinition(word) { log("Réponse brute de l'API :", apiResponse); if (!Array.isArray(apiResponse) || apiResponse.length === 0) { - console.warn(`Aucune définition trouvée pour "${word}"`); - return []; // Retourne un tableau vide si aucune définition + console.warn(`Aucune définition trouvée pour "${word}"`); + return []; // Retourne un tableau vide si aucune définition } - // Formatage des données - const formattedData = formatDefinitionData(apiResponse); - log("Données formatées :", formattedData); + // 🛠Formatage des données récupérées + formattedData = formatDefinitionData(apiResponse); + log(" Données formatées :", formattedData); + + // Vérification avant retour + if (!formattedData.definitions) { + formattedData.definitions = []; // + } + if (!formattedData.pronunciations) { + formattedData.pronunciations = []; // + } + if (!formattedData.definitionsByPOS) { + formattedData.definitionsByPOS = {}; // + } return [ - { - source: "Wiktionnaire", - text: formattedData.definitions.length > 0 ? formattedData.definitions.join(" | ") : "âš ï¸ Aucune définition disponible." - } + { + source: "Wiktionnaire", + text: formattedData.definitions.length > 0 ? formattedData.definitions.join(" | ") : "Aucune définition disponible.", + pronunciations: formattedData.pronunciations, + definitionsByPOS: formattedData.definitionsByPOS + } ]; } } catch (error) { - log("Erreur lors de la récupération de la définition :", error); - return [{ source: "Wiktionnaire", text: "Erreur lors de la récupération sur le Wiktionnaire." }]; + log("⌠Erreur lors de la récupération de la définition :", error); + return [{ + source: "Wiktionnaire", + text: "Erreur lors de la récupération sur le Wiktionnaire.", + pronunciations: [], + definitionsByPOS: {} + }]; } } -/** - * Récupère la définition d'un mot depuis l'API de BaLex (Wiktionnaire). - * @param {string} word - Le mot dont on veut la définition. - * @returns {Promise<object[]>} - Un tableau d'objets contenant la réponse de l'API. - */ + + async function wikiApiResponse(word) { const result = await browser.storage.local.get("accessToken"); authToken = result.accessToken; @@ -218,173 +251,340 @@ async function wikiApiResponse(word) { } } -/** - * Formate les données de la réponse de l'API de BaLex (Wiktionnaire). - * @param {object[]} apiResponse - La réponse de l'API de BaLex (Wiktionnaire). - * @returns {object} - Un objet contenant les données formatées. - */ function formatDefinitionData(apiResponse) { let formattedData = { word: apiResponse[0]?.id.split("-").slice(2).join("-") || "", - pronunciations: [], - definitions: [], - examples: [], - translations: {}, + pronunciations: new Set(), // Utilisation d'un Set pour éviter les doublons + definitionsByPOS: {} // Organisation par catégorie grammaticale }; apiResponse.forEach(entry => { - const wordData = entry[entry.id.split(".").slice(-1)[0]]; // Accéder aux données via la clé dynamique + const wordData = entry[entry.id.split(".").slice(-1)[0]]; // Accès aux données via clé dynamique + const pos = wordData.pos || "Autre"; // Définit le POS ou "Autre" si absent + + if (!formattedData.definitionsByPOS[pos]) { + formattedData.definitionsByPOS[pos] = { + definitions: [], + examples: [], + pronunciations: new Set() + }; + } + + // Ajout des prononciations globales en extrayant les transcriptions valides + if (wordData.pronunciations) { + wordData.pronunciations.forEach(pron => { + if (pron.transcript) { + formattedData.pronunciations.add(pron.transcript); + } + }); + } - // Ajout des prononciations + // Ajout des prononciations spécifiques au POS if (wordData.pronunciations) { - formattedData.pronunciations.push(...wordData.pronunciations); + wordData.pronunciations.forEach(pron => { + if (pron.transcript) { + formattedData.definitionsByPOS[pos].pronunciations.add(pron.transcript); + } + }); } - // Ajout des définitions + // Ajout des définitions et des exemples 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)); + formattedData.definitionsByPOS[pos].definitions.push( + ...sense.Definitions.map(d => d.definition) + ); } if (sense.Examples) { - formattedData.examples.push(...sense.Examples.map(e => e.example)); + formattedData.definitionsByPOS[pos].examples.push( + ...sense.Examples.map(e => e.example) + ); } } } + }); - // 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); - }); - } + // Convertir les Sets en Arrays + formattedData.pronunciations = [...formattedData.pronunciations]; + Object.keys(formattedData.definitionsByPOS).forEach(pos => { + formattedData.definitionsByPOS[pos].pronunciations = [...formattedData.definitionsByPOS[pos].pronunciations]; }); return formattedData; } + + + // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Affichage des définitions dans la barre latérale (lexiques + Wiktionnaire) +// â–Œ Affichage des définitions dans la barre latérale // ───────────────────────────────────────────────────────────────────────────── -const MAX_LENGTH = 200; +const MAX_LENGTH = 300; /** - * Affiche les définitions dans la barre latérale. - * @param {object[]} definitions - Les définitions à afficher. - */ +* Affiche les définitions dans la barre latérale. +*/ function displayDefinitions(definitions) { log("Affichage des définitions reçues :", definitions); if (!Array.isArray(definitions)) return; - const mesLexiquesList = document.getElementById("mesLexiquesList"); const wiktionnaireList = document.getElementById("wiktionnaireList"); const noLexiconDefinitionsContainer = document.getElementById("noLexiconDefinitionsContainer"); const noWiktionaryDefinitionsContainer = document.getElementById("noWiktionaryDefinitionsContainer"); + const balexIdMap = new Map(); // Dictionnaire pour stocker balex_id par lexique + + // Nettoyage des listes existantes mesLexiquesList.innerHTML = ""; wiktionnaireList.innerHTML = ""; + // Masquer les sections vides par défaut if (noLexiconDefinitionsContainer) noLexiconDefinitionsContainer.style.display = "none"; if (noWiktionaryDefinitionsContainer) noWiktionaryDefinitionsContainer.style.display = "none"; + let hasLexiconDefinitions = false; let hasWiktionaryDefinitions = false; - const lexiconGroups = {}; - definitions.forEach(({ source, text }) => { - if (!source || !text) return; - - const li = document.createElement("li"); - let displayedText = text; - - if (text.length > MAX_LENGTH) { - displayedText = text.slice(0, MAX_LENGTH) + "... "; - const readMoreLink = document.createElement("a"); - readMoreLink.href = "#"; - readMoreLink.textContent = "[Lire la suite]"; - readMoreLink.style.marginLeft = "5px"; - readMoreLink.style.color = "#8d5c70"; - readMoreLink.style.textDecoration = "underline"; - readMoreLink.style.cursor = "pointer"; - readMoreLink.addEventListener("click", (event) => { - event.preventDefault(); - openDefinitionPopup(text); - }); - li.appendChild(document.createTextNode(displayedText)); - li.appendChild(readMoreLink); - } else { - li.textContent = displayedText; - } + definitions.forEach(({ source, text, balex_id, definitionsByPOS }) => { + if (!source || !text) return; + log(`Traitement de ${source}, balex_id:`, balex_id); - // Vérifier la source : Wiktionnaire/Lexiques - if (source === "Wiktionnaire") { - wiktionnaireList.appendChild(li); - hasWiktionaryDefinitions = true; - } else { - if (!lexiconGroups[source]) { - lexiconGroups[source] = []; + const definitionContainer = document.createElement("div"); + definitionContainer.classList.add("definition-item"); + + // Gestion des définitions des lexiques + if (!definitionsByPOS) { + // C'est une définition provenant des lexiques utilisateur + const li = document.createElement("li"); + li.textContent = text; + definitionContainer.appendChild(li); + + // Stocker le premier `balex_id` trouvé pour chaque lexique + if (!balexIdMap.has(source) && balex_id) { + balexIdMap.set(source, balex_id); + } + // Ajout dans le bon groupe + if (!lexiconGroups[source]) { + lexiconGroups[source] = []; + } + lexiconGroups[source].push(definitionContainer); + hasLexiconDefinitions = true; + + } else { + // C'est une définition provenant du Wiktionnaire + log(`Traitement des définitions du Wiktionnaire pour "${source}"`); + + // 2. Affichage des prononciations globales si disponibles + const allPronunciations = new Set(); + Object.values(definitionsByPOS).forEach(posData => { + posData.pronunciations.forEach(pron => allPronunciations.add(pron)); + }); + + if (allPronunciations.size > 0) { + const pronDiv = document.createElement("div"); + pronDiv.style.marginBottom = "5px"; + pronDiv.style.fontSize = "13px"; + pronDiv.textContent = "Prononciations possibles :"; + pronDiv.style.marginBottom = "10px"; + definitionContainer.appendChild(pronDiv); + + // Création d'un conteneur pour les prononciations + const pronContainer = document.createElement("div"); + pronContainer.style.display = "flex"; + pronContainer.style.justifyContent = "center"; + pronContainer.style.flexWrap = "wrap"; + allPronunciations.forEach(pron => { + const pronSpan = document.createElement("span"); + pronSpan.textContent = pron; + pronSpan.style.marginRight = "25px"; + pronSpan.style.fontSize = "15px"; + pronSpan.style.alignItems = "center"; + pronSpan.style.justifyContent = "center"; + pronContainer.appendChild(pronSpan); + }); + definitionContainer.appendChild(pronContainer); + } + + + // 3. Affichage des définitions triées par POS + Object.entries(definitionsByPOS).forEach(([pos, posData]) => { + if (posData.definitions.length === 0) return; // Évite les POS vides + + // Création d'un conteneur dédié pour ce POS + const posContainer = document.createElement("div"); + + // Titre du POS + const posTitle = document.createElement("h4"); + posTitle.style.marginTop = "10px"; + posTitle.style.color = "#FFFFFF"; + posTitle.textContent = pos.toUpperCase(); + posContainer.appendChild(posTitle); + + // Prononciations spécifiques au POS + if (posData.pronunciations.length > 0) { + const posPronDiv = document.createElement("div"); + posPronDiv.style.fontStyle = "italic"; + posPronDiv.style.color = "#94608a"; + // posPronDiv.textContent = posData.pronunciations.join(", "); + posContainer.appendChild(posPronDiv); + } + // Récupération des définitions complètes + const fullDefinitions = posData.definitions.map(def => def.trim()); + // Concaténation de toutes les définitions dans un seul texte (séparées par un espace) + const concatenatedText = fullDefinitions.join(" "); + + // Création de la liste des définitions pour ce POS + const defList = document.createElement("ul"); + defList.style.margin = "0"; + defList.style.paddingLeft = "20px"; + + const li = document.createElement("li"); + if (concatenatedText.length > MAX_LENGTH) { + // Affichage tronqué pour l'ensemble du bloc de définitions + const truncatedText = concatenatedText.slice(0, MAX_LENGTH) + "... "; + li.textContent = truncatedText; + + // Bouton "Lire la suite" pour afficher le contenu complet + const readMoreLink = document.createElement("a"); + readMoreLink.href = "#"; + readMoreLink.textContent = "[Lire la suite]"; + readMoreLink.style.marginLeft = "5px"; + readMoreLink.style.color = "#8d5c70"; + readMoreLink.style.textDecoration = "underline"; + readMoreLink.style.cursor = "pointer"; + readMoreLink.addEventListener("click", (event) => { + event.preventDefault(); + // Construction du contenu complet pour ce POS en préservant la structure + let popupContent = `<h4 style="margin-top:10px; color:#FFFFFF;">${pos.toUpperCase()}</h4>`; + if (posData.pronunciations.length > 0) { + popupContent += `<div style="font-style:italic; color:#94608a;">${posData.pronunciations.join(", ")}</div>`; + } + popupContent += "<ul style='margin:0; padding-left:20px;'>"; + fullDefinitions.forEach(text => { + popupContent += `<li>${text}</li>`; + }); + popupContent += "</ul>"; + openDefinitionPopup(popupContent); + }); + li.appendChild(readMoreLink); + } else { + li.textContent = concatenatedText; + } + + defList.appendChild(li); + posContainer.appendChild(defList); + definitionContainer.appendChild(posContainer); + }); + + wiktionnaireList.appendChild(definitionContainer); + hasWiktionaryDefinitions = true; } - lexiconGroups[source].push(li); - hasLexiconDefinitions = true; - } }); + // 5. Gestion des groupes de lexiques personnels Object.entries(lexiconGroups).forEach(([lexiconName, definitionItems]) => { const lexiconContainer = document.createElement("div"); lexiconContainer.className = "lexicon-section"; - + + // Création d'un conteneur pour l'en-tête et l'icône (l'icône est en dehors de l'en-tête) + const headerContainer = document.createElement("div"); + headerContainer.className = "header-container"; + headerContainer.style.position = "relative"; // Pour positionner l'icône absolument + headerContainer.style.cursor = "pointer"; + + // Création de l'en-tête qui contiendra le titre seul const lexiconHeader = document.createElement("div"); lexiconHeader.className = "lexicon-header"; - lexiconHeader.textContent = lexiconName; - lexiconHeader.addEventListener("click", () => { + + // Ajout du titre + const titleSpan = document.createElement("span"); + titleSpan.textContent = lexiconName; + lexiconHeader.appendChild(titleSpan); + + // Ajout de l'en-tête dans le conteneur + headerContainer.appendChild(lexiconHeader); + + // Création de l'icône BaLex en dehors de l'en-tête + const balexButton = createBaLexButton(lexiconName, balexIdMap); + if (balexButton) { + // Positionnement absolu pour que l'icône reste à droite du conteneur + balexButton.style.position = "absolute"; + balexButton.style.top = "50%"; + balexButton.style.transform = "translateY(-50%)"; + balexButton.style.right = "5px"; // Ajustez cet espace si besoin + + // Empêche que le clic sur l'icône ne déclenche le toggle du contenu + balexButton.addEventListener("click", (e) => e.stopPropagation()); + headerContainer.appendChild(balexButton); + } + + // Clic sur le conteneur pour dérouler ou masquer les définitions + headerContainer.addEventListener("click", () => { lexiconContent.classList.toggle("hidden"); }); - - const lexiconContent = document.createElement("ul"); + + // Création du conteneur pour les définitions (contenu déroulant) + const lexiconContent = document.createElement("div"); lexiconContent.className = "lexicon-content hidden"; - - definitionItems.forEach(li => lexiconContent.appendChild(li)); - - lexiconContainer.appendChild(lexiconHeader); + definitionItems.forEach(item => lexiconContent.appendChild(item)); + + lexiconContainer.appendChild(headerContainer); lexiconContainer.appendChild(lexiconContent); mesLexiquesList.appendChild(lexiconContainer); }); + + // 6. Gestion des sections vides if (!hasLexiconDefinitions && noLexiconDefinitionsContainer) { - if (!authToken) { - noLexiconDefinitionsContainer.textContent = "Veuillez vous connecter pour accéder aux définitions de vos lexiques."; - noLexiconDefinitionsContainer.style.textAlign = "center"; - noLexiconDefinitionsContainer.style.fontStyle = "italic"; - } else { - noLexiconDefinitionsContainer.textContent = "Aucune définition trouvée dans les lexiques."; - } - noLexiconDefinitionsContainer.style.display = "block"; + noLexiconDefinitionsContainer.style.display = "block"; } if (!hasWiktionaryDefinitions && noWiktionaryDefinitionsContainer) { - noWiktionaryDefinitionsContainer.style.display = "block"; + noWiktionaryDefinitionsContainer.style.display = "block"; } +} - const mesLexiquesContainer = document.getElementById("mesLexiquesContainer"); - if (mesLexiquesContainer) { - mesLexiquesContainer.style.display = hasLexiconDefinitions ? "block" : "none"; - } - const wiktionnaireContainer = document.getElementById("wiktionnaireContainer"); - if (wiktionnaireContainer) { - wiktionnaireContainer.style.display = hasWiktionaryDefinitions ? "block" : "none"; + +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Gestion du popup pour afficher la définition complète du Wiktionnaire +// ───────────────────────────────────────────────────────────────────────────── + +function openDefinitionPopup(fullText) { + const modalOverlay = document.getElementById("modalOverlay"); + const modalFullText = document.getElementById("modalFullText"); + if (!modalOverlay || !modalFullText) { + log("Modal elements not found!"); + return; } + modalFullText.innerHTML = "<p>" + fullText.replace(/\n/g, "<br>") + "</p>"; + modalOverlay.style.display = "flex"; } /** - * Récupère en parallèle : - * - les définitions des lexiques de l'utilisateur (fetchLexiconDefinitions) - * - la définition Wiktionnaire (fetchWiktionaryDefinition) - * Puis fusionne les résultats. - */ +* Ferme le popup et nettoie le contenu +*/ +function closeDefinitionPopup() { + const modalOverlay = document.getElementById("modalOverlay"); + const modalFullText = document.getElementById("modalFullText"); + if (!modalOverlay || !modalFullText) return; + modalOverlay.style.display = "none"; + modalFullText.innerHTML = ""; +} + +// ───────────────────────────────────────────────────────────────────────────── +// â–Œ Affichage des définitions Babalex + Wiktionnaire +// ───────────────────────────────────────────────────────────────────────────── + +/** +* Récupère en parallèle : +* - les définitions des lexiques de l'utilisateur (fetchLexiconDefinitions) +* - la définition Wiktionnaire (fetchWiktionaryDefinition) +* Puis fusionne les résultats. +*/ async function combineDefinitions(word) { log(`[combineDefinitions] Récupération des définitions pour "${word}"...`); @@ -517,54 +717,61 @@ function displayLexiconResults(lexicons) { li.innerHTML = `<strong>${lexiconName}</strong>`; ul.appendChild(li); - log(`✅ Lexique ajouté : ${lexiconName} (ID: ${lexicon.id})`); + log(`Lexique ajouté : ${lexiconName} (ID: ${lexicon.id})`); }); resultDiv.appendChild(ul); } -// ───────────────────────────────────────────────────────────────────────────── -// â–Œ Gestion de la boîte modale pour afficher la définition complète du Wiktionnaire -// ───────────────────────────────────────────────────────────────────────────── /** - * Ouvre la boîte modale pour afficher la définition complète du Wiktionnaire. - * @param {string} fullText - La définition complète du mot. + * Crée un bouton permettant d'ouvrir une entrée dans BaLex. + * @param {string} lexiconName - Le nom du lexique. + * @param {Map} balexIdMap - Une Map contenant les balex_id associés aux lexiques. + * @returns {HTMLElement|null} - Un bouton HTML ou null si `balex_id` est manquant. */ -function openDefinitionPopup(fullText) { - const modalOverlay = document.getElementById("modalOverlay"); - const modalFullText = document.getElementById("modalFullText"); - if (!modalOverlay || !modalFullText) { - log("Modal elements not found!"); - return; +function createBaLexButton(lexiconName, balexIdMap) { + const balexServ = "babalex.lezinter.net"; + const balex_id = balexIdMap.get(lexiconName) || null; + + log(`ID BaLex trouvé pour "${lexiconName}" :`, balex_id); + + // Vérifie si `balex_id` est valide + if (!balex_id) { + console.warn(`âš ï¸ Aucun balex_id trouvé pour "${lexiconName}". Bouton non créé.`); + return null; } - modalFullText.innerHTML = "<p>" + fullText.replace(/\n/g, "<br>") + "</p>"; - modalOverlay.style.display = "flex"; -} -/** - * Ferme la boîte modale et nettoie le contenu - * @returns {void} - */ -function closeDefinitionPopup() { - const modalOverlay = document.getElementById("modalOverlay"); - const modalFullText = document.getElementById("modalFullText"); - if (!modalOverlay || !modalFullText) return; - modalOverlay.style.display = "none"; - modalFullText.innerHTML = ""; + const balexUrl = `https://${balexServ}/entry/${balex_id}/show`; + + // Création du bouton + const balexButton = document.createElement("img"); + balexButton.src = "../assets/icons/Link_to_balex.svg"; + balexButton.classList.add("balex-icon"); + balexButton.alt = "Icône BaLex"; + balexButton.style.cursor = "help"; + balexButton.style.width = "15px"; + balexButton.style.height = "15px"; + balexButton.style.marginLeft = "10px"; + + // Ajout du lien + balexButton.addEventListener("click", (event) => { + event.stopPropagation(); // Pour éviter de déclencher le clic sur le conteneur parent + window.open(balexUrl, "_blank"); + }); + + return balexButton; } // ───────────────────────────────────────────────────────────────────────────── -// â–Œ Exposition des fonctions pour un usage global +// â–Œ Utilisation des fonctions dans d'autres scripts // ───────────────────────────────────────────────────────────────────────────── window.fetchLexiconDefinitions = fetchLexiconDefinitions; window.fetchWiktionaryDefinition = fetchWiktionaryDefinition; -window.wikiApiResponse = wikiApiResponse; -window.formatDefinitionData = formatDefinitionData; window.displayDefinitions = displayDefinitions; +window.openDefinitionPopup = openDefinitionPopup; +window.closeDefinitionPopup = closeDefinitionPopup; window.combineDefinitions = combineDefinitions; window.showDefinitions = showDefinitions; window.fetchDefinition = fetchDefinition; window.displayLexiconResults = displayLexiconResults; -window.openDefinitionPopup = openDefinitionPopup; -window.closeDefinitionPopup = closeDefinitionPopup; diff --git a/src/utils/highlighting.js b/src/utils/highlighting.js index 1851fda0fdb44aac5f4f59bb3d4aee12d48209c0..6391763a890f1b149bec182845e403574d205273 100644 --- a/src/utils/highlighting.js +++ b/src/utils/highlighting.js @@ -84,11 +84,11 @@ log("Initialisation de highlighting.js"); // ───────────────────────────────────────────────────────────── (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(); diff --git a/src/utils/stats.js b/src/utils/stats.js index 114c53ed90c7abbafefe5521fe129f64c856bfff..1ced5bf503bc3e4db1bb694775e9176af4bc88b0 100644 --- a/src/utils/stats.js +++ b/src/utils/stats.js @@ -1,10 +1,8 @@ -log("Stats.js chargé."); (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 @@ -22,9 +20,17 @@ log("Stats.js chargé."); if (message.command === "threshold-exceeded") { 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)) { + if (typeof message.wordsAboveThreshold !== "object" || message.wordsAboveThreshold === null) { + return; + } + for (const [lang, words] of Object.entries(message.wordsAboveThreshold)) { + if (!Array.isArray(words)) { + continue; + } + alertMessage += `\n🔹 ${lang.toUpperCase()} : ${words.join(", ")}`; } + alert(alertMessage); } }); @@ -125,9 +131,9 @@ log("Stats.js chargé."); */ function startReadingTimer(element, text, minReadTime) { if (!readingTimers.has(element)) { - let elapsedTime = 0; - let counter = null; - + let elapsedTime = 0; + let counter = null; + // Créer l'indicateur uniquement si on est en mode debug if (DEBUG) { counter = document.createElement("div"); @@ -141,31 +147,31 @@ log("Stats.js chargé."); 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) { - log(`[Stats] Élément lu : ${text}`); - readContent.add(text); - sendTextToWorker(text); - stopReadingTimer(element, text); - } - + elapsedTime += 1000; + + // Vérifie si l'utilisateur est actif et si le temps minimum est atteint + if (userIsActive && elapsedTime >= minReadTime) { + log(`[Stats] Élément lu : ${text}`); + readContent.add(text); + sendTextToWorker(text); + stopReadingTimer(element, text); + } + // Mise à jour de la position et du contenu du compteur si en debug if (DEBUG && counter) { - let rect = element.getBoundingClientRect(); - counter.style.top = `${rect.top + window.scrollY - 20}px`; - counter.style.left = `${rect.left + window.scrollX + rect.width + 10}px`; - counter.innerText = `â³ ${Math.floor(elapsedTime / 1000)}s`; - } - }, 1000); - - readingTimers.set(element, { interval, counter, elapsedTime }); + 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 @@ -175,8 +181,8 @@ log("Stats.js chargé."); let { interval, counter } = readingTimers.get(element); clearInterval(interval); if (counter) { - counter.remove(); - } + counter.remove(); + } readingTimers.delete(element); } } @@ -198,7 +204,7 @@ log("Stats.js chargé."); */ function removeReadingIndicator(element) { if (readingTimers.has(element)) { - let { counter } = readingTimers.get(element); + let { counter } = readingTimers.get(element); if (counter) { counter.remove(); } diff --git a/src/workers/pyodide_worker.js b/src/workers/pyodide_worker.js index 469b419bd92c1bbd643390131deec98763ec0722..8131c19d0c0bfe3000271d0f01b2a70122d9a2aa 100644 --- a/src/workers/pyodide_worker.js +++ b/src/workers/pyodide_worker.js @@ -10,238 +10,337 @@ let pyodide = null; 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 = {}; // Mots déjà notifiés (pour éviter les doublons) -let stoplistFr = new Set(); // Stoplist pour le français +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 -// ───────────────────────────────────────────────────────────── -// â–Œ Fonction de chargement de Pyodide et installation de simplemma -// ───────────────────────────────────────────────────────────── -async function loadPyodideAndSimplemma() { - // Chargement de Pyodide - if (!pyodideLoaded) { - log("[Worker] Chargement de Pyodide..."); - try { - importScripts(`${LATEST_BASE_URL}pyodide.js`); - pyodide = await loadPyodide({ indexURL: LATEST_BASE_URL }); - await pyodide.loadPackage("lzma"); - await pyodide.loadPackage("micropip"); - pyodideLoaded = true; - log("[Worker] Pyodide chargé avec succès !"); - } catch (err) { - log("[Worker] Erreur lors de l'import de pyodide.js :", err); - throw err; - } +// --- Attente de la mise à jour de la stoplist --- +let stoplistsReady = new Promise((resolve) => resolve()); + +// Écouteur des messages reçus du background script +self.onmessage = async (event) => { + const { command, ...data } = event.data; + log("[WebWorker] Message reçu du Background:", command, data); + + switch (command) { + + case "pyodide-simplemma": + if (pyodideLoaded && simplemmaLoaded) { + log("[Worker] Pyodide et Simplemma déjà chargés."); + self.postMessage({ type: "pyodide-simplemma", status: "already_loaded", message: "Pyodide et Simplemma déjà en mémoire" }); + return; + } + try { + if (!pyodideLoaded) { + log("[Worker] Chargement de Pyodide..."); + try { + 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; + log("[Worker] Pyodide chargé avec succès !"); + } + + if (!simplemmaLoaded) { + log("[Worker] Installation de simplemma..."); + 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 = "Simplemma est prêt" + 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; + 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() }); + } + break; + + case "process-text": + if (!pyodideLoaded) { + log("[Worker] Pyodide non chargé."); + self.postMessage({ type: "process-text", status: "error", message: "Pyodide pas encore chargé" }); + return; + } + + log("[Worker] Texte reçu pour analyse :", data.text); + try { + const result = await pyodide.runPythonAsync(` + import json + import re + import simplemma + from simplemma import langdetect + + 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()) + + text = """${data.text.replace(/\"/g, '\\"')}""" + detected_lang = detect_language(text) + if detected_lang == "unk": + detected_lang = "other" + + tokens = tokenize(text) + lemmatized_tokens = [simplemma.lemmatize(token, lang=detected_lang) for token in tokens] + + freq = {} + for token in lemmatized_tokens: + 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); + } + break; + + case "update-preferences": + userThreshold = data.threshold; + trackedLanguages = data.trackedLanguages; + autoAddEnabled = data.autoAdd; + isAuthenticated = data.isAuthenticated; + log("[Worker] Mise à jour des préférences :", { userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated }); + break; + + case "update-lexicons": + userLexicons = JSON.parse(data.lexicons) + log("[Worker] Lexiques mis à jour :", userLexicons); + break; + + case "update-auth-token": + authToken = data.accessToken; + log("[Worker] Token mis à jour :", authToken ? "Disponible" : "Aucun token reçu"); + break; + + case "update-stoplist": + stoplistsReady = new Promise((resolve) => { + if (data.stoplists && typeof data.stoplists === "object") { + stoplistsByLang = {}; + for (const [lang, words] of Object.entries(data.stoplists)) { + stoplistsByLang[lang] = new Set(words.map(word => word.toLowerCase().trim())); + log(`[Worker] Stoplist mise à jour pour '${lang}' : ${stoplistsByLang[lang].size} mots.`); + } + } else { + log("[Worker] âš Stoplists reçues incorrectes ou vides."); + } + resolve(); // Stoplist prête + }); + break; + + case "update-include-stopwords": + includeStopwords = data.includeStopwords; + log(`[Worker] Mise à jour de includeStopwords : ${includeStopwords}`); + break; } +}; + + +// --- Vérification du seuil et notification --- +let pendingWords = {}; // Stocker temporairement les mots en attente d'ajout +let addWordTimeout = null; // Timer pour regrouper les ajouts + +async function checkThreshold(lang) { + await stoplistsReady; // Attendre que les stoplists soient chargées + + if (!autoAddEnabled || !isAuthenticated) { + log("[Worker] âš Auto-Add désactivé ou utilisateur non connecté."); + } else if (!trackedLanguages.includes(lang)) { + log(`[Worker] âš La langue '${lang}' n'est pas suivie.`); + } else { + log(`[Worker] Vérification des fréquences pour la langue '${lang}'...`); + + const stoplist = stoplistsByLang[lang] || new Set(); + const shouldFilterStopwords = stoplist.size > 0 && includeStopwords; + + log(`[Worker] 📠Stoplist pour '${lang}' : ${shouldFilterStopwords ? "Appliquée" : "Non appliquée"}`); + + const wordsFrequencies = storedFrequencies[lang] || {}; + const notifiedSet = new Set(notifiedWords[lang] || []); - // Installation de simplemma - if (!simplemmaLoaded) { - log("[Worker] Installation de simplemma..."); - try { - 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; - log("[Worker] Simplemma installé avec succès !"); - } catch (err) { - log("[Worker] Erreur lors de l'installation de simplemma :", err); - throw err; + // Filtrer les mots qui dépassent le seuil + const exceededWords = Object.entries(wordsFrequencies) + .filter(([word, count]) => count >= userThreshold && !notifiedSet.has(word)) + .map(([word]) => word); + + if (exceededWords.length === 0) { + log(`[Worker] Aucun mot dépassant le seuil pour '${lang}'.`); + } else { + // Filtrer selon la stoplist si nécessaire + const finalWords = shouldFilterStopwords + ? exceededWords.filter(word => { + const isInStoplist = stoplist.has(word); + if (isInStoplist) log(`[Worker] Mot "${word}" exclu (stoplist)`); + return !isInStoplist; + }) + : exceededWords; + + if (finalWords.length === 0) { + log(`[Worker] Tous les mots dépassant le seuil pour '${lang}' sont dans la stoplist.`); + } else { + // Ajouter les mots aux sets et logs + notifiedWords[lang] = notifiedSet; + finalWords.forEach(word => notifiedSet.add(word)); + + log("Mots dépassant le seuil :", finalWords); + self.postMessage({ type: "threshold-exceeded", wordsAboveThreshold: finalWords }); + + // Ajout aux mots en attente pour un envoi groupé + if (!pendingWords[lang]) pendingWords[lang] = []; + pendingWords[lang].push(...finalWords); + + // Regrouper les ajouts en une seule tâche différée + if (!addWordTimeout) { + addWordTimeout = setTimeout(processPendingWords, 3000); + } + } } } } -// ───────────────────────────────────────────────────────────── -// â–Œ Gestion des messages reçus du background -// ───────────────────────────────────────────────────────────── -self.onmessage = async (event) => { - const data = event.data; - log("[WebWorker] Message reçu du Background:", data); - - // ─────────────── - // Commande : Chargement de Pyodide et Simplemma - // ─────────────── - if (data.command === "pyodide-simplemma") { - if (pyodideLoaded && simplemmaLoaded) { - log("[Worker] Pyodide et Simplemma déjà chargés."); - self.postMessage({ - type: "pyodide-simplemma", - status: "already_loaded", - message: "Pyodide et Simplemma déjà en mémoire" - }); - return; - } - try { - await loadPyodideAndSimplemma(); - self.postMessage({ - type: "pyodide-simplemma", - status: "success", - message: "Pyodide et Simplemma chargés" - }); - } catch (error) { - log("[Worker] Erreur lors du chargement de Pyodide ou Simplemma :", error); - self.postMessage({ - type: "pyodide-simplemma", - status: "error", - message: error.toString() - }); +//Traiter les ajouts groupés +async function processPendingWords() { + log("Traitement des mots à ajouter en lot..."); + + for (const lang in pendingWords) { + const words = pendingWords[lang]; + for (const word of words) { + await addWordToLexicon(lang, word); } - return; } - // ─────────────── - // Commande : Traitement du texte - // ─────────────── - if (data.command === "process-text") { - if (!pyodideLoaded || !simplemmaLoaded) { - const errorMessage = "Les statistiques ne sont pas encore activées. Veuillez patienter..."; - log("[Worker] Pyodide non chargé."); - self.postMessage({ - type: "process-text", - status: "error", - message: errorMessage - }); - return; - } - log("[Worker] Texte reçu pour analyse :", data.text); - try { - const stoplistArray = Array.from(stoplistFr); - log("Stoplist utilisée :", stoplistArray); - const result = await pyodide.runPythonAsync(` -import json -import re -import simplemma -from simplemma import langdetect - -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) { - log("[Worker] Erreur dans l'analyse du texte :", error); - } + // Réinitialiser la file d'attente et le timeout + pendingWords = {}; + addWordTimeout = null; +} + +async function addWordToLexicon(lang, word) { + if (!authToken) { + log("Impossible d'ajouter le mot : Aucun token d’authentification."); return; } - // ─────────────── - // Commande : Mise à jour des préférences utilisateur - // ─────────────── - if (data.command === "update-preferences") { - userThreshold = data.threshold; - trackedLanguages = data.trackedLanguages; - autoAddEnabled = data.autoAdd; - isAuthenticated = data.isAuthenticated; - log("[Worker] Mise à jour des préférences :", { userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated }); + log(`Tentative d'ajout du mot '${word}' pour la langue '${lang}'`); + + const stoplist = stoplistsByLang[lang] || new Set(); + // Vérifier si on a une stoplist et si l'utilisateur veut exclure les stopwords + const shouldFilterStopwords = stoplist.size > 0 && !includeStopwords; + + // Si le filtrage est activé et que le mot est un stopword, on ne l'ajoute pas + if (shouldFilterStopwords && stoplist.has(word)) { + log(`Mot '${word}' ignoré car présent dans la stoplist.`); return; } - // ─────────────── - // Commande : Mise à jour de la stoplist - // ─────────────── - if (data.command === "update-stoplist") { - stoplistFr = new Set(data.stoplist.map(word => word.toLowerCase().trim())); - log("[Worker] Stoplist FR mise à jour :", stoplistFr); + // Trouver les lexiques correspondant à la langue détectée + const targetLexicons = userLexicons + .filter(lexicon => lexicon.language === lang && lexicon.category === "User") + .map(lexicon => lexicon.id); + + if (targetLexicons.length === 0) { + log(`Aucun lexique trouvé pour la langue '${lang}'. Impossible d'ajouter '${word}'.`); return; } -}; -// ───────────────────────────────────────────────────────────── -// â–Œ Vérification du seuil et notification -// ───────────────────────────────────────────────────────────── -function checkThreshold(lang) { - if (!autoAddEnabled || !isAuthenticated) { - log("[Worker] Auto-Add désactivé ou utilisateur non connecté. Aucune vérification de seuil."); + try { + log(`Envoi de '${word}' aux lexiques ${targetLexicons}...`); + await AddWord(authToken, word, targetLexicons); + // Notifier le background de l'ajout réussi + self.postMessage({ type: "word-added", word, language: lang, lexicons: targetLexicons }); + } catch (error) { + console.error(`Erreur lors de l'ajout du mot '${word}':`, error); + } +} + +//TODO : trouver un moyen d'accéder à cette fonction depuis api.js +async function AddWord(authToken, selectedWord, lexiconIds, force = false) { + if (!authToken) { + console.error("Aucun token d’authentification fourni."); return; } - if (!trackedLanguages.includes(lang)) { - log(`[Worker] La langue ${lang} n'est pas suivie.`); + if (!selectedWord) { + console.error("Aucun mot spécifié pour l’ajout."); return; } - 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 (!Array.isArray(lexiconIds) || lexiconIds.length === 0) { + console.error("Aucun lexique sélectionné pour l’ajout."); + return; } - if (Object.keys(wordsAboveThreshold).length > 0) { - log("[Worker] Nouveaux mots dépassant le seuil :", wordsAboveThreshold); - self.postMessage({ - type: "threshold-exceeded", - wordsAboveThreshold: wordsAboveThreshold + + const url = "https://babalex.lezinter.net/api/entry/create"; + const body = { + graphy: selectedWord, + force, + target_lex: lexiconIds + }; + + log("Envoi de la requête API AddWord :", body); + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${authToken}` + }, + body: JSON.stringify(body) }); - } else { - log("[Worker] Aucun nouveau mot n'a dépassé le seuil."); + + if (!response.ok) { + throw new Error(`Erreur API (${response.status}): ${response.statusText}`); + } + + log(`Mot '${selectedWord}' ajouté avec succès aux lexiques ${lexiconIds}`); + return await response.json(); + } catch (error) { + console.error(`Erreur lors de l'ajout du mot '${selectedWord}':`, error); } }