ADDED gc_lang/fr/webext/content_scripts/event.js Index: gc_lang/fr/webext/content_scripts/event.js ================================================================== --- /dev/null +++ gc_lang/fr/webext/content_scripts/event.js @@ -0,0 +1,175 @@ +// Injected script into web page + +"use strict"; + + +function uniqueID () { + let nMin = Math.ceil(0); + let nMax = Math.floor(9999999); + return Date.now().toString(36) + "-" + (Math.floor(Math.random() * (nMax - nMin)) + nMin).toString(36); +} + +// ! Ecoute des messages venant du content-script +let sBrowserURL; +document.addEventListener("GrammalecteToPage", function respListener (event) { + //console.log(event); + let oData = JSON.parse(event.detail); + // Message envoyer dès que le script est injecté + if (typeof oData.init !== "undefined") { + sBrowserURL = oData.init; + } + if (typeof oData.tiny !== "undefined") { + //console.log('Detect Tiny', oData.tiny); + TinyIDInPage(oData.tiny); + } + //console.log("GrammalecteToPage", oData); +}); + +// ! Permet d'envoyer des message vers le content script +// Retourne un identifiant unique au cas ou si besoin +// La ID unique peut être util si on permet d'intérogé grammalecte sans zone +function sendToGrammalecte (oDataAction) { + let oDataToSend = oDataAction; + if (typeof oDataToSend.sActionId === "undefined") { + oDataToSend.sActionId = uniqueID(); + } + if (oDataAction.elm) { + if (!oDataAction.elm.id) { + oDataAction.elm.id = uniqueID(); + } + oDataToSend.elm = oDataAction.elm.id; + } + //console.log('oDataToSend', oDataToSend); + let eventGrammalecte = new CustomEvent("GrammalecteEvent", { detail: JSON.stringify(oDataToSend) }); + document.dispatchEvent(eventGrammalecte); + return oDataToSend.sActionId; +} + +// ! Envoie de l’information que l'injection est bien faite ;) +// (peut être lu aussi bien par la page web que le content script) +var customAPILoaded = new CustomEvent("GLInjectedScriptIsReady"); +document.dispatchEvent(customAPILoaded); + +// Gros Hack : Auto add a button in tinymce ;) + +// Page to test v4 https://www.quackit.com/html/html_editors/tinymce_editor.cfm +// https://www.responsivefilemanager.com/demo.php + +// Page to test v3 http://www.imathas.com/editordemo/demo.html + +function TinyOnEditor (event, editor=null) { + let xEditorAdd = editor || event.editor; + //console.log(xEditorAdd); + if (typeof xEditorAdd.settings.Grammalecte === "undefined") { + let aBtn; + let cPlugSep; + let bIsAdded = false; + if (tinyMCE.majorVersion >= 4) { + cPlugSep = " "; + aBtn = ["toolbar3", "toolbar2", "toolbar1", "toolbar"]; + } else if (tinyMCE.majorVersion >= 3) { + cPlugSep = ","; + aBtn = ["theme_advanced_buttons3", "theme_advanced_buttons2", "theme_advanced_buttons1", "theme_advanced_buttons1_add_before"]; + } + + let sBtn; + let iBtn = 0; + for (sBtn of aBtn) { + if (!bIsAdded && (typeof xEditorAdd.settings[sBtn] !== "undefined" || iBtn == aBtn.length)) { + bIsAdded = true; + if (typeof xEditorAdd.settings[sBtn] !== "undefined" && xEditorAdd.settings[sBtn] !== "") { + xEditorAdd.settings[sBtn] = (xEditorAdd.settings[sBtn] + cPlugSep + "Grammalecte").trim(); + } else { + let m = /(.*)([0-9])/.exec(sBtn); + if (m.length === 3 && parseInt(m[2]) > 1 && xEditorAdd.settings[sBtn] === "") { + sBtn = m[1] + (parseInt(m[2]) - 1); + xEditorAdd.settings[sBtn] = (xEditorAdd.settings[sBtn] + cPlugSep + "Grammalecte").trim(); + } else { + xEditorAdd.settings[sBtn] = "Grammalecte"; + } + } + } + iBtn++; + } + if (!bIsAdded) { + //Valeur par defaut + xEditorAdd.settings[sBtn] = + "undo redo | styleselect | bold italic | alignleft" + + " aligncenter alignright alignjustify | " + + " bullist numlist outdent indent | link image" + + " Grammalecte"; + } + xEditorAdd.settings["Grammalecte"] = true; + } + + xEditorAdd.addButton("Grammalecte", { + text: "", + icon: false, + image: sBrowserURL + "img/logo-16.png", + //"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAC8UlEQVQ4jX3TbUgTcRwH8P89ddu5u9tt082aZmpFEU4tFz0QGTUwCi0heniR9MSUIKRaD0RvIlKigsooo+iNFa0XJYuwIjEK19OcDtPElsG0ktyp591t7u7+vUh7MPX3+vf5/n8/+P0BmKJIPUUVlh2rdVVeesWlzEybqg+bFOsoylnqPmNavGFfknV2Omu2Lvja3vxAURKJib3opHizu8riLK6gjRyuKgmoSoMRFENRUqfXTzvBGK62LC2uoFkOl4RhjQ8+qWt7dPNE3sbdp+2LXbsGe9qb4rIo/BfwFy6nWQ4ThWGNDzbcfu29dMDh2nHU7CypYNLmzTda0/L5cNuzmDQi/A4Y27k6eQxLI79wS/11D0AAMNvs6XT6ojVJjJEgTbMy2BT77xBMp09KcpaWV1uc41jQoi0NdUHfjeOO9WWn7AVF7s7n986SithPJGeupBh2PCSP/xxqxAp3eq6wuUV7Wc6MSZIEhA8vHjbfOe/OcW3zmAuKy+nUzAyD2bow8ODaEROFq8AyZ5WBYdEZXGqGxZ61HJV+9HYCJRbTNA0QBA40HWunaKN5dKg/DBKxeCIe09Th/m4MJwiMSZmLEzMQAABQRuNqgu8NYX3doTcMpvCkLbtQZ2AJkrPOZG1zlnY13T+Hy9EehY90h57eqcorcZ/lctZuMzAsOjLEqwNv66/6vZcPYRBC+C3cGaBxhSet2av1BpYgTTY7k5y2JPT41slIR6Axv8R9nnOs+4Pf+2r992uOxGVJwgAAAEINfgt3BGgsESWtWas1iGDyl+CT/u7WpvxNFRc4x7qtBoZFhSFejb7z1fq9NYfjsiT+cwcQavBruCOgU4SIGo18amuoq3Js3FNlynVtH385+s53ze+t8cRkURx3yMTTRBAEQVAUXbFlf3XystJKA2NExeFBdWASDAAA+MQACCEEmqbJ0b6PMC7JwhDU8YFHV5u9NZ64LErT/oW/63tPV6uJwmKoOND78u7Fg5NhAAD4CVbzY9cwrWQrAAAAAElFTkSuQmCC", + onclick: function (e) { + //console.log(xEditorAdd.getContent()); + //console.log(xEditorAdd.getBody().innerText); + let sText = xEditorAdd.getBody().innerText; + let iframeElement; + if (typeof xEditorAdd.iframeElement !== "undefined" && typeof xEditorAdd.iframeElement.id !== "undefined") { + iframeElement = xEditorAdd.iframeElement.id; + } else if (typeof xEditorAdd.editorId !== "undefined") { + iframeElement = xEditorAdd.editorId + "_ifr"; + } + sendToGrammalecte({ sTextToParse: sText, iframe: iframeElement }); + } + }); +} + +function TinyInPage () { + for (let i = tinyMCE.editors.length - 1; i > -1; i--) { + let sTinyId = tinyMCE.editors[i].id; + if (typeof tinyMCE.editors[i].settings.Grammalecte === "undefined") { + TinyIDInPage(sTinyId); + } + } +} + +function TinyIDInPage (sTinyId) { + if (tinyMCE.majorVersion >= 4) { + tinyMCE.EditorManager.execCommand("mceFocus", true, sTinyId); + tinyMCE.EditorManager.execCommand("mceRemoveEditor", true, sTinyId); + tinyMCE.EditorManager.execCommand("mceAddEditor", false, sTinyId); + } else if (tinyMCE.majorVersion >= 3) { + tinyMCE.execCommand("mceFocus", false, sTinyId); + tinyMCE.execCommand("mceRemoveControl", true, sTinyId); + tinyMCE.execCommand("mceAddControl", false, sTinyId); + } +} + +if (typeof tinyMCE !== "undefined" && tinyMCE.majorVersion && tinyMCE.majorVersion >= 3 && tinyMCE.majorVersion <= 5) { + //console.log("Have TinyMCE"); + if (tinyMCE.majorVersion >= 4) { + tinyMCE.on("AddEditor", TinyOnEditor); + } else if (tinyMCE.majorVersion >= 3) { + tinyMCE.onAddEditor.add(TinyOnEditor); + } + try { + TinyInPage(); + } catch (e) { + console.error(e); + } +} + +/* // ! In the webpage script : +document.addEventListener('GLInjectedScriptIsReady', function() { + // Le gestionnaire d'évènement est prêt! + // La page web peut effectuer des actions + ... +}); +... +// Pour demander une correction sur le texte +sendToGrammalecte({"sTextToParse": "salut comment ca vaa?"}); +// Pour demander une correction sur un élément html +sendToGrammalecte({"sTextToParse": true, "elm": elementHTML}); +// Pour avoir le lexicographe sur un texte +sendToGrammalecte({"sTextForLexicographer": "salut comment ca vaa?"}); +// Pour avoir le lexicographe sur un élément html +sendToGrammalecte({"sTextForLexicographer": true, "elm": elementHTML}); +*/ Index: gc_lang/fr/webext/content_scripts/init.js ================================================================== --- gc_lang/fr/webext/content_scripts/init.js +++ gc_lang/fr/webext/content_scripts/init.js @@ -11,12 +11,12 @@ No SharedWorker, no images allowed for now… */ "use strict"; - function showError (e) { + // console can’t display error objects from content scripts console.error(e.fileName + "\n" + e.name + "\nline: " + e.lineNumber + "\n" + e.message); } // Chrome don’t follow the W3C specification: // https://browserext.github.io/browserext/ @@ -23,11 +23,10 @@ let bChrome = false; if (typeof(browser) !== "object") { var browser = chrome; bChrome = true; } - /* function loadImage (sContainerClass, sImagePath) { let xRequest = new XMLHttpRequest(); xRequest.open('GET', browser.extension.getURL("")+sImagePath, false); @@ -40,13 +39,13 @@ oElem.appendChild(img); }); } */ +let oTinyAdd = {}; const oGrammalecte = { - nMenu: 0, lMenu: [], oTFPanel: null, oLxgPanel: null, @@ -102,20 +101,56 @@ observePage: function () { /* When a textarea is added via jascript we add the menu :) */ + function NodeTinyMCE (xNode) { + let parentNode = xNode.parentNode; //mutation.target + if ( + typeof xNode !== "undefined" && + typeof xNode.id !== "undefined" && + typeof oTinyAdd[xNode.id] === "undefined" && + (parentNode.classList.contains("mce-edit-area") || parentNode.classList.contains("mceIframeContainer")) + ) { + //console.log(oTinyAdd, xNode, parentNode, parentNode.classList); + oTinyAdd[xNode.id] = true; + sendToWebpage({ tiny: xNode.id.replace("_ifr", "") }); + } + } this.xObserver = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { - for (let i = 0; i < mutation.addedNodes.length; i++){ - if (mutation.addedNodes[i].tagName == "TEXTAREA") { - oGrammalecte.lMenu.push(new GrammalecteMenu(oGrammalecte.nMenu, mutation.addedNodes[i])); + for (let i = 0; i < mutation.addedNodes.length; i++) { + let MutationNode = mutation.addedNodes[i]; + let tagName = MutationNode.tagName; + + if (tagName == "TEXTAREA") { + oGrammalecte.lMenu.push(new GrammalecteMenu(oGrammalecte.nMenu, MutationNode)); + oGrammalecte.nMenu += 1; + } else if (tagName == "IFRAME") { + NodeTinyMCE(MutationNode); + } else if ((tagName == "DIV" || tagName == "SPAN") && MutationNode.hasAttribute && MutationNode.hasAttribute("contenteditable")) { + oGrammalecte.lMenu.push(new GrammalecteMenu(oGrammalecte.nMenu, MutationNode)); oGrammalecte.nMenu += 1; - } else if (mutation.addedNodes[i].getElementsByTagName) { - for (let xNode of mutation.addedNodes[i].getElementsByTagName("textarea")) { + } else if (MutationNode.getElementsByTagName) { + for (let xNode of MutationNode.getElementsByTagName("textarea")) { oGrammalecte.lMenu.push(new GrammalecteMenu(oGrammalecte.nMenu, xNode)); oGrammalecte.nMenu += 1; + } + for (let xNode of MutationNode.getElementsByTagName("div")) { + if (xNode.hasAttribute && xNode.hasAttribute("contenteditable")){ + oGrammalecte.lMenu.push(new GrammalecteMenu(oGrammalecte.nMenu, xNode)); + oGrammalecte.nMenu += 1; + } + } + for (let xNode of MutationNode.getElementsByTagName("span")) { + if (xNode.hasAttribute && xNode.hasAttribute("contenteditable")){ + oGrammalecte.lMenu.push(new GrammalecteMenu(oGrammalecte.nMenu, xNode)); + oGrammalecte.nMenu += 1; + } + } + for (let xNode of MutationNode.getElementsByTagName("iframe")) { + NodeTinyMCE(xNode); } } } }); }); @@ -353,5 +388,100 @@ break; default: console.log("[Content script] Unknown command: " + sActionDone); } }); + + +/* + Communicate webpage script <=> WebExtension + La méthode d’injection de ce script est importante ! + + Pour que la page web puisse envoyer des infos au background + Page web => Script injecté => Content script => Background + Pour la réponse, ce sont les mêmes étapes en sens inverse. +*/ +let xScriptNode = document.createElement("script"); +xScriptNode.src = browser.extension.getURL("content_scripts/event.js"); +document.documentElement.appendChild(xScriptNode); + +let nMin = Math.ceil(0); +let nMax = Math.floor(9999999); +function uniqueID () { + return Date.now().toString(36) + "-" + (Math.floor(Math.random() * (nMax - nMin)) + nMin).toString(36); +} + +// ! Écoute des messages venant du script injecté +document.addEventListener("GrammalecteEvent", function (event) { + let oActionFromPage = JSON.parse(event.detail); + //console.log(event); + let sText = false; + let dInfo = {}; + let xNodeToParse = null; + + if (oActionFromPage.iframe) { + xNodeToParse = document.getElementById(oActionFromPage.iframe).contentWindow.document.body; + } + if (oActionFromPage.elm) { + xNodeToParse = document.getElementById(oActionFromPage.elm); + sText = xNodeToParse.tagName == "TEXTAREA" ? xNodeToParse.value : xNodeToParse.innerText; + dInfo = { sTextAreaId: xNodeToParse.id }; + } + if (oActionFromPage.sTextToParse) { + oGrammalecte.startGCPanel(xNodeToParse); + xGrammalectePort.postMessage({ + sCommand: "parseAndSpellcheck", + dParam: { sText: sText || oActionFromPage.sTextToParse, sCountry: "FR", bDebug: false, bContext: false }, + dInfo: dInfo + }); + } + if (oActionFromPage.sTextForLexicographer) { + oGrammalecte.startLxgPanel(); + xGrammalectePort.postMessage({ + sCommand: "getListOfTokens", + dParam: { sText: sText || oActionFromPage.sTextForLexicographer }, + dInfo: dInfo + }); + } +}); + +let bInjectedScriptReady = false; +let lBufferMsg = []; + +// ! Permet d’envoyer des messages vers le script injecté +// (peut aussi être lu par un script sur la page web) +function sendToWebpage (oDataAction) { + let oDataToSend = oDataAction; + if (typeof oDataToSend.sActionId === "undefined") { + oDataToSend.sActionId = uniqueID(); + } + if (oDataAction.elm) { + if (!oDataAction.elm.id) { + oDataAction.elm.id = uniqueID(); + } + oDataToSend.elm = oDataAction.elm.id; + } + + if (!bInjectedScriptReady) { + lBufferMsg.push(oDataToSend); + } else { + //console.log('sendToWebpage', oDataToSend); + let eventGrammalecte = new CustomEvent("GrammalecteToPage", { detail: JSON.stringify(oDataToSend) }); + document.dispatchEvent(eventGrammalecte); + } + + return oDataToSend.sActionId; +} + +// ! Les messages ne peuvent être envoyés qu’après l’injection du script +document.addEventListener("GLInjectedScriptIsReady", function () { + //console.log("GLInjectedScriptIsReady EXT"); + bInjectedScriptReady = true; + if (lBufferMsg.length > 0) { + for (const oDataToSend of lBufferMsg) { + let eventGrammalecte = new CustomEvent("GrammalecteToPage", { detail: JSON.stringify(oDataToSend) }); + document.dispatchEvent(eventGrammalecte); + } + } +}); + +sendToWebpage({ init: browser.extension.getURL("") }); Index: gc_lang/fr/webext/manifest.json ================================================================== --- gc_lang/fr/webext/manifest.json +++ gc_lang/fr/webext/manifest.json @@ -112,10 +112,11 @@ "grammalecte/graphspell/_dictionaries/fr-reform.json", "grammalecte/fr/conj_data.json", "grammalecte/fr/mfsp_data.json", "grammalecte/fr/phonet_data.json", "grammalecte/fr/tests_data.json", + "content_scripts/event.js", "img/logo-16.png" ], "permissions": [ "activeTab",