ADDED gc_lang/fr/mailext/README.txt Index: gc_lang/fr/mailext/README.txt ================================================================== --- gc_lang/fr/mailext/README.txt +++ gc_lang/fr/mailext/README.txt @@ -0,0 +1,14 @@ + += GRAMMALECTE = + +French grammar checker +By Olivier R. (olivier /at/ grammalecte /dot/ net) + +Website: https://grammalecte.net/ + +License: GPL 3 -- http://www.gnu.org/copyleft/gpl.html + +Grammalecte for Firefox is a derivative tool born from the version +for LibreOffice written in Python. + +Written in JavaScript ES6/ES7. ADDED gc_lang/fr/mailext/background.js Index: gc_lang/fr/mailext/background.js ================================================================== --- gc_lang/fr/mailext/background.js +++ gc_lang/fr/mailext/background.js @@ -0,0 +1,204 @@ +// Background + +"use strict"; + +console.log("THUNDERBIRD!!! BACKGROUND"); + + +const oWorkerHandler = { + xGCEWorker: null, + + nLastTimeWorkerResponse: 0, // milliseconds since 1970-01-01 + + oTask: {}, + + start: function () { + this.xGCEWorker = new Worker("gce_worker.js"); + this.xGCEWorker.onmessage = function (e) { + // Messages received from the Worker + // https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent + try { + this.nLastTimeWorkerResponse = Date.now(); + let {sActionDone, result, dInfo, bEnd, bError} = e.data; + if (bError) { + console.log(result); + console.log(dInfo); + return; + } + switch (sActionDone) { + case "init": + storeGCOptions(result); + break; + case "parse": + case "parseAndSpellcheck": + case "parseAndSpellcheck1": + case "parseFull": + case "getListOfTokens": + case "getSpellSuggestions": + case "getVerb": + // send result to content script + if (typeof(dInfo.iReturnPort) === "number") { + let xPort = dConnx.get(dInfo.iReturnPort); + xPort.postMessage(e.data); + } else { + console.log("[background] don’t know where to send results"); + console.log(e.data); + } + break; + case "textToTest": + case "fullTests": + // send result to panel + browser.runtime.sendMessage(e.data); + break; + case "getOptions": + case "getDefaultOptions": + case "resetOptions": + // send result to panel + storeGCOptions(result); + browser.runtime.sendMessage(e.data); + break; + case "setOptions": + case "setOption": + storeGCOptions(result); + break; + case "setDictionary": + case "setDictionaryOnOff": + //console.log("[background] " + sActionDone + ": " + result); + break; + default: + console.log("[background] Unknown command: " + sActionDone); + console.log(e.data); + } + } + catch (error) { + showError(error); + console.log(e.data); + } + }; + }, + + getTimeSinceLastResponse: function () { + // result in seconds + return Math.floor((Date.now() - this.nLastTimeWorkerResponse) / 1000); + }, + + restart: function (nDelay=5) { + if (this.getTimeSinceLastResponse() <= nDelay) { + console.log("Worker not restarted. Worked ", nDelay, " seconds ago."); + return false; + } + if (this.xGCEWorker) { + this.xGCEWorker.terminate(); + } + this.start(); + oInitHandler.initGrammarChecker(); + sendCommandToAllTabs("workerRestarted"); + console.log("Worker restarted."); + return true; + }, + + addTask: function () { + // + }, + + closeTask: function () { + // + } +} + + +const oInitHandler = { + + initUIOptions: function () { + browser.storage.local.get("ui_options").then(this._initUIOptions, showError); + browser.storage.local.get("autorefresh_option").then(this._initUIOptions, showError); + }, + + initGrammarChecker: function () { + browser.storage.local.get("gc_options").then(this._initGrammarChecker, showError); + browser.storage.local.get("personal_dictionary").then(this._setSpellingDictionaries, showError); + browser.storage.local.get("community_dictionary").then(this._setSpellingDictionaries, showError); + browser.storage.local.get("oPersonalDictionary").then(this._setSpellingDictionaries, showError); // deprecated + browser.storage.local.get("sc_options").then(this._initSCOptions, showError); + }, + + _initUIOptions: function (oSavedOptions) { + if (!oSavedOptions.hasOwnProperty("ui_options")) { + browser.storage.local.set({"ui_options": { + textarea: true, + editablenode: true + }}); + } + if (!oSavedOptions.hasOwnProperty("autorefresh_option")) { + browser.storage.local.set({"autorefresh_option": true}); + } + }, + + _initGrammarChecker: function (oSavedOptions) { + try { + let dOptions = (oSavedOptions.hasOwnProperty("gc_options")) ? oSavedOptions.gc_options : null; + if (dOptions !== null && Object.getOwnPropertyNames(dOptions).length == 0) { + console.log("# Error: the saved options was an empty object."); + dOptions = null; + } + oWorkerHandler.xGCEWorker.postMessage({ + sCommand: "init", + dParam: {sExtensionPath: browser.extension.getURL(""), dOptions: dOptions, sContext: "Firefox"}, + dInfo: {} + }); + } + catch (e) { + console.log("initGrammarChecker failed"); + showError(e); + } + }, + + _setSpellingDictionaries: function (oData) { + if (oData.hasOwnProperty("community_dictionary")) { + oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionary", dParam: { sDictionary: "community", oDict: oData["community_dictionary"] }, dInfo: {} }); + } + if (oData.hasOwnProperty("personal_dictionary")) { + oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionary", dParam: { sDictionary: "personal", oDict: oData["personal_dictionary"] }, dInfo: {} }); + } + }, + + _initSCOptions: function (oData) { + if (!oData.hasOwnProperty("sc_options")) { + browser.storage.local.set({"sc_options": { + community: true, + personal: true + }}); + oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionaryOnOff", dParam: { sDictionary: "community", bActivate: true }, dInfo: {} }); + oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionaryOnOff", dParam: { sDictionary: "personal", bActivate: true }, dInfo: {} }); + } else { + oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionaryOnOff", dParam: { sDictionary: "community", bActivate: oData.sc_options["community"] }, dInfo: {} }); + oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionaryOnOff", dParam: { sDictionary: "personal", bActivate: oData.sc_options["personal"] }, dInfo: {} }); + } + } +} + +// start the Worker for the GC +oWorkerHandler.start(); + +// init the options stuff and start the GC +oInitHandler.initUIOptions(); +oInitHandler.initGrammarChecker(); + + + +/* + Actions +*/ + +function storeGCOptions (dOptions) { + if (dOptions instanceof Map) { + dOptions = helpers.mapToObject(dOptions); + } + browser.storage.local.set({"gc_options": dOptions}); +} + + +function showError (e) { + console.error(e); + //console.error(e.fileName + "\n" + e.name + "\nline: " + e.lineNumber + "\n" + e.message); +} ADDED gc_lang/fr/mailext/gce_worker.js Index: gc_lang/fr/mailext/gce_worker.js ================================================================== --- gc_lang/fr/mailext/gce_worker.js +++ gc_lang/fr/mailext/gce_worker.js @@ -0,0 +1,428 @@ +/* + WORKER: + https://developer.mozilla.org/en-US/docs/Web/API/Worker + https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope + + + JavaScript sucks. + No module available in WebExtension at the moment! :( + No require, no import/export. + + In Worker, we have importScripts() which imports everything in this scope. + + In order to use the same base of code with XUL-addon for Thunderbird and SDK-addon for Firefox, + all modules have been “objectified”. And while they are still imported via “require” + in the previous extensions, they are loaded as background scripts in WebExtension sharing + the same memory space… + + When JavaScript become a modern language, “deobjectify” the modules… + + ATM, import/export are not available by default: + — Chrome 60 – behind the Experimental Web Platform flag in chrome:flags. + — Firefox 54 – behind the dom.moduleScripts.enabled setting in about:config. + — Edge 15 – behind the Experimental JavaScript Features setting in about:flags. + + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export +*/ + +"use strict"; + + +//console.log("[Worker] GC Engine Worker [start]"); +//console.log(self); + +importScripts("grammalecte/graphspell/helpers.js"); +importScripts("grammalecte/graphspell/str_transform.js"); +importScripts("grammalecte/graphspell/char_player.js"); +importScripts("grammalecte/graphspell/suggest.js"); +importScripts("grammalecte/graphspell/ibdawg.js"); +importScripts("grammalecte/graphspell/spellchecker.js"); +importScripts("grammalecte/text.js"); +importScripts("grammalecte/graphspell/tokenizer.js"); +importScripts("grammalecte/fr/conj.js"); +importScripts("grammalecte/fr/mfsp.js"); +importScripts("grammalecte/fr/phonet.js"); +importScripts("grammalecte/fr/cregex.js"); +importScripts("grammalecte/fr/gc_options.js"); +importScripts("grammalecte/fr/gc_rules.js"); +importScripts("grammalecte/fr/gc_rules_graph.js"); +importScripts("grammalecte/fr/gc_engine.js"); +importScripts("grammalecte/fr/lexicographe.js"); +importScripts("grammalecte/tests.js"); +/* + Warning. + Initialization can’t be completed at startup of the worker, + for we need the path of the extension to load data stored in JSON files. + This path is retrieved in background.js and passed with the event “init”. +*/ + + +function createResponse (sActionDone, result, dInfo, bEnd, bError=false) { + return { + "sActionDone": sActionDone, + "result": result, // can be of any type + "dInfo": dInfo, + "bEnd": bEnd, + "bError": bError + }; +} + +function createErrorResult (e, sDescr="no description") { + return { + "sType": "error", + "sDescription": sDescr, + "sMessage": e.fileName + "\n" + e.name + "\nline: " + e.lineNumber + "\n" + e.message + }; +} + +function showData (e) { + for (let sParam in e) { + console.log(sParam); + console.log(e[sParam]); + } +} + + +/* + Message Event Object + https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent +*/ +onmessage = function (e) { + let {sCommand, dParam, dInfo} = e.data; + switch (sCommand) { + case "init": + init(dParam.sExtensionPath, dParam.dOptions, dParam.sContext, dInfo); + break; + case "parse": + parse(dParam.sText, dParam.sCountry, dParam.bDebug, dParam.bContext, dInfo); + break; + case "parseAndSpellcheck": + parseAndSpellcheck(dParam.sText, dParam.sCountry, dParam.bDebug, dParam.bContext, dInfo); + break; + case "parseAndSpellcheck1": + parseAndSpellcheck1(dParam.sText, dParam.sCountry, dParam.bDebug, dParam.bContext, dInfo); + break; + case "parseFull": + parseFull(dParam.sText, dParam.sCountry, dParam.bDebug, dParam.bContext, dInfo); + break; + case "getListOfTokens": + getListOfTokens(dParam.sText, dInfo); + break; + case "getOptions": + getOptions(dInfo); + break; + case "getDefaultOptions": + getDefaultOptions(dInfo); + break; + case "setOptions": + setOptions(dParam.sOptions, dInfo); + break; + case "setOption": + setOption(dParam.sOptName, dParam.bValue, dInfo); + break; + case "resetOptions": + resetOptions(dInfo); + break; + case "textToTest": + textToTest(dParam.sText, dParam.sCountry, dParam.bDebug, dParam.bContext, dInfo); + break; + case "fullTests": + fullTests(dInfo); + break; + case "setDictionary": + setDictionary(dParam.sDictionary, dParam.oDict, dInfo); + break; + case "setDictionaryOnOff": + setDictionaryOnOff(dParam.sDictionary, dParam.bActivate, dInfo); + break; + case "getSpellSuggestions": + getSpellSuggestions(dParam.sWord, dInfo); + break; + case "getVerb": + getVerb(dParam.sVerb, dParam.bPro, dParam.bNeg, dParam.bTpsCo, dParam.bInt, dParam.bFem, dInfo); + break; + default: + console.log("[Worker] Unknown command: " + sCommand); + showData(e.data); + } +} + + + +let bInitDone = false; + +let oSpellChecker = null; +let oTokenizer = null; +let oLxg = null; +let oTest = null; +let oLocution = null; + + +/* + Technical note: + This worker don’t work as a PromiseWorker (which returns a promise), so when we send request + to this worker, we can’t wait the return of the answer just after the request made. + The answer is received by the background in another function (onmessage). + That’s why the full text to analyze is send in one block, but analyse is returned paragraph + by paragraph. +*/ + +function init (sExtensionPath, dOptions=null, sContext="JavaScript", dInfo={}) { + try { + if (!bInitDone) { + console.log("[Worker] Loading… Extension path: " + sExtensionPath); + conj.init(helpers.loadFile(sExtensionPath + "/grammalecte/fr/conj_data.json")); + phonet.init(helpers.loadFile(sExtensionPath + "/grammalecte/fr/phonet_data.json")); + mfsp.init(helpers.loadFile(sExtensionPath + "/grammalecte/fr/mfsp_data.json")); + //console.log("[Worker] Modules have been initialized…"); + gc_engine.load(sContext, "sCSS", sExtensionPath+"grammalecte/graphspell/_dictionaries"); + oSpellChecker = gc_engine.getSpellChecker(); + oTest = new TestGrammarChecking(gc_engine, sExtensionPath+"/grammalecte/fr/tests_data.json"); + oTokenizer = new Tokenizer("fr"); + oLocution = helpers.loadFile(sExtensionPath + "/grammalecte/fr/locutions_data.json"); + oLxg = new Lexicographe(oSpellChecker, oTokenizer, oLocution); + if (dOptions !== null) { + if (!(dOptions instanceof Map)) { + dOptions = helpers.objectToMap(dOptions); + } + gc_engine.setOptions(dOptions); + } + //tests(); + bInitDone = true; + } else { + console.log("[Worker] Already initialized…") + } + // we always retrieve options from the gc_engine, for setOptions filters obsolete options + dOptions = helpers.mapToObject(gc_engine.getOptions()); + postMessage(createResponse("init", dOptions, dInfo, true)); + } + catch (e) { + console.error(e); + postMessage(createResponse("init", createErrorResult(e, "init failed"), dInfo, true, true)); + } +} + + +function parse (sText, sCountry, bDebug, bContext, dInfo={}) { + sText = sText.replace(/­/g, "").normalize("NFC"); + for (let sParagraph of text.getParagraph(sText)) { + let aGrammErr = gc_engine.parse(sParagraph, sCountry, bDebug, bContext); + postMessage(createResponse("parse", aGrammErr, dInfo, false)); + } + postMessage(createResponse("parse", null, dInfo, true)); +} + +function parseAndSpellcheck (sText, sCountry, bDebug, bContext, dInfo={}) { + let i = 0; + sText = sText.replace(/­/g, "").normalize("NFC"); + for (let sParagraph of text.getParagraph(sText)) { + let aGrammErr = gc_engine.parse(sParagraph, sCountry, bDebug, null, bContext); + let aSpellErr = oSpellChecker.parseParagraph(sParagraph); + postMessage(createResponse("parseAndSpellcheck", {sParagraph: sParagraph, iParaNum: i, aGrammErr: aGrammErr, aSpellErr: aSpellErr}, dInfo, false)); + i += 1; + } + postMessage(createResponse("parseAndSpellcheck", null, dInfo, true)); +} + +function parseAndSpellcheck1 (sParagraph, sCountry, bDebug, bContext, dInfo={}) { + sParagraph = sParagraph.replace(/­/g, "").normalize("NFC"); + let aGrammErr = gc_engine.parse(sParagraph, sCountry, bDebug, null, bContext); + let aSpellErr = oSpellChecker.parseParagraph(sParagraph); + postMessage(createResponse("parseAndSpellcheck1", {sParagraph: sParagraph, aGrammErr: aGrammErr, aSpellErr: aSpellErr}, dInfo, true)); +} + +function parseFull (sText, sCountry, bDebug, bContext, dInfo={}) { + let i = 0; + sText = sText.replace(/­/g, "").normalize("NFC"); + for (let sParagraph of text.getParagraph(sText)) { + let lSentence = gc_engine.parse(sParagraph, sCountry, bDebug, null, bContext, true); + console.log("*", lSentence); + postMessage(createResponse("parseFull", {sParagraph: sParagraph, iParaNum: i, lSentence: lSentence}, dInfo, false)); + i += 1; + } + postMessage(createResponse("parseFull", null, dInfo, true)); +} + +function getListOfTokens (sText, dInfo={}) { + // lexicographer + try { + sText = sText.replace(/­/g, "").normalize("NFC"); + for (let sParagraph of text.getParagraph(sText)) { + if (sParagraph.trim() !== "") { + postMessage(createResponse("getListOfTokens", oLxg.getListOfTokensReduc(sParagraph, true), dInfo, false)); + } + } + postMessage(createResponse("getListOfTokens", null, dInfo, true)); + } + catch (e) { + console.error(e); + postMessage(createResponse("getListOfTokens", createErrorResult(e, "no tokens"), dInfo, true, true)); + } +} + +function getOptions (dInfo={}) { + let dOptions = helpers.mapToObject(gc_engine.getOptions()); + postMessage(createResponse("getOptions", dOptions, dInfo, true)); +} + +function getDefaultOptions (dInfo={}) { + let dOptions = helpers.mapToObject(gc_engine.getDefaultOptions()); + postMessage(createResponse("getDefaultOptions", dOptions, dInfo, true)); +} + +function setOptions (dOptions, dInfo={}) { + if (!(dOptions instanceof Map)) { + dOptions = helpers.objectToMap(dOptions); + } + gc_engine.setOptions(dOptions); + dOptions = helpers.mapToObject(gc_engine.getOptions()); + postMessage(createResponse("setOptions", dOptions, dInfo, true)); +} + +function setOption (sOptName, bValue, dInfo={}) { + console.log(sOptName+": "+bValue); + if (sOptName) { + gc_engine.setOption(sOptName, bValue); + let dOptions = helpers.mapToObject(gc_engine.getOptions()); + postMessage(createResponse("setOption", dOptions, dInfo, true)); + } +} + +function resetOptions (dInfo={}) { + gc_engine.resetOptions(); + let dOptions = helpers.mapToObject(gc_engine.getOptions()); + postMessage(createResponse("resetOptions", dOptions, dInfo, true)); +} + +function tests () { + console.log(conj.getConj("devenir", ":E", ":2s")); + console.log(mfsp.getMasForm("emmerdeuse", true)); + console.log(mfsp.getMasForm("pointilleuse", false)); + console.log(phonet.getSimil("est")); + let aRes = gc_engine.parse("Je suit..."); + for (let oErr of aRes) { + console.log(text.getReadableError(oErr)); + } +} + +function textToTest (sText, sCountry, bDebug, bContext, dInfo={}) { + if (!gc_engine) { + postMessage(createResponse("textToTest", "# Grammar checker not loaded.", dInfo, true)); + return; + } + sText = sText.replace(/­/g, "").normalize("NFC"); + let aGrammErr = gc_engine.parse(sText, sCountry, bDebug, bContext); + let sMsg = ""; + for (let oErr of aGrammErr) { + sMsg += text.getReadableError(oErr) + "\n"; + } + if (sMsg == "") { + sMsg = "Aucune erreur détectée."; + } + postMessage(createResponse("textToTest", sMsg, dInfo, true)); +} + +function fullTests (dInfo={}) { + if (!gc_engine) { + postMessage(createResponse("fullTests", "# Grammar checker not loaded.", dInfo, true)); + return; + } + let dMemoOptions = gc_engine.getOptions(); + let dTestOptions = gc_engine.getDefaultOptions(); + dTestOptions.set("nbsp", true); + dTestOptions.set("esp", true); + dTestOptions.set("unit", true); + dTestOptions.set("num", true); + gc_engine.setOptions(dTestOptions); + let sMsg = ""; + for (let sRes of oTest.testParse()) { + sMsg += sRes + "\n"; + console.log(sRes); + } + gc_engine.setOptions(dMemoOptions); + postMessage(createResponse("fullTests", sMsg, dInfo, true)); +} + + +// SpellChecker + +function setDictionary (sDictionary, oDict, dInfo) { + if (!oSpellChecker) { + postMessage(createResponse("setDictionary", "# Error. SpellChecker not loaded.", dInfo, true)); + return; + } + //console.log("setDictionary", sDictionary); + switch (sDictionary) { + case "main": + oSpellChecker.setMainDictionary(oDict); + break; + case "community": + oSpellChecker.setCommunityDictionary(oDict); + break; + case "personal": + oSpellChecker.setPersonalDictionary(oDict); + break; + default: + console.log("[worker] setDictionary: Unknown dictionary <"+sDictionary+">"); + } + postMessage(createResponse("setDictionary", true, dInfo, true)); +} + +function setDictionaryOnOff (sDictionary, bActivate, dInfo) { + if (!oSpellChecker) { + postMessage(createResponse("setDictionary", "# Error. SpellChecker not loaded.", dInfo, true)); + return; + } + //console.log("setDictionaryOnOff", sDictionary, bActivate); + switch (sDictionary) { + case "community": + if (bActivate) { + oSpellChecker.activateCommunityDictionary(); + } else { + oSpellChecker.deactivateCommunityDictionary(); + } + break; + case "personal": + if (bActivate) { + oSpellChecker.activatePersonalDictionary(); + } else { + oSpellChecker.deactivatePersonalDictionary(); + } + break; + default: + console.log("[worker] setDictionaryOnOff: Unknown dictionary <"+sDictionary+">"); + } + postMessage(createResponse("setDictionaryOnOff", true, dInfo, true)); +} + +function getSpellSuggestions (sWord, dInfo) { + if (!oSpellChecker) { + postMessage(createResponse("getSpellSuggestions", "# Error. SpellChecker not loaded.", dInfo, true)); + return; + } + let i = 0; + for (let aSugg of oSpellChecker.suggest(sWord)) { + postMessage(createResponse("getSpellSuggestions", {sWord: sWord, aSugg: aSugg, iSuggBlock: i}, dInfo, true)); + i += 1; + } +} + + +// Conjugueur + +function getVerb (sWord, bPro, bNeg, bTpsCo, bInt, bFem, dInfo) { + try { + let oVerb = null; + let oConjTable = null; + if (conj.isVerb(sWord)) { + oVerb = new Verb(sWord); + oConjTable = oVerb.createConjTable(bPro, bNeg, bTpsCo, bInt, bFem); + } + postMessage(createResponse("getVerb", { oVerb: oVerb, oConjTable: oConjTable }, dInfo, true)); + } + catch (e) { + console.error(e); + postMessage(createResponse("getVerb", createErrorResult(e, "no verb"), dInfo, true, true)); + } +} ADDED gc_lang/fr/mailext/icon.png Index: gc_lang/fr/mailext/icon.png ================================================================== --- gc_lang/fr/mailext/icon.png +++ gc_lang/fr/mailext/icon.png cannot compute difference between binary files ADDED gc_lang/fr/mailext/manifest.json Index: gc_lang/fr/mailext/manifest.json ================================================================== --- gc_lang/fr/mailext/manifest.json +++ gc_lang/fr/mailext/manifest.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 2, + "applications": { + "gecko": { + "id": "${tb_identifier}", + "strict_min_version": "68.0a1" + } + }, + "name": "${tb_name}", + "description": "${description}", + "version": "${version}", + + "author": "${author}", + "homepage_url": "${link}", + + "background": { + "scripts": [ + "grammalecte-js/graphspell/helpers.js", + "background.js" + ] + }, + + "permissions": [ + "storage" + ] +}