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 @@ -47,10 +47,11 @@ const oGrammalecte = { nButton: 0, lButton: [], + oPanelButton: null, oTFPanel: null, oGCPanel: null, oMessageBox: null, @@ -62,11 +63,19 @@ oOptions: null, bAutoRefresh: true, - listenRightClick: function () { + listen: function () { + document.addEventListener("click", (xEvent) => { + //console.log("click", xEvent.target.id); + this.oPanelButton.examineNode(xEvent.target); + }); + document.addEventListener("keyup", (xEvent) => { + //console.log("keyup", document.activeElement.id); + this.oPanelButton.examineNode(document.activeElement); + }); // Node where a right click is done // Bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1325814 document.addEventListener('contextmenu', (xEvent) => { this.xRightClickedNode = xEvent.target; }, true); @@ -74,96 +83,15 @@ clearRightClickedNode: function () { this.xRightClickedNode = null; }, - createButtons: function () { - if (bChrome) { - browser.storage.local.get("ui_options", this._prepareButtons.bind(this)); - return; - } - browser.storage.local.get("ui_options").then(this._prepareButtons.bind(this), showError); - }, - - _isEligibleNode: function (xNode) { - return (xNode.style.display !== "none" && xNode.style.visibility !== "hidden" - && !(xNode.dataset.grammalecte_button && xNode.dataset.grammalecte_button == "false")) - }, - - _prepareButtons: function (oOptions) { - if (oOptions.hasOwnProperty("ui_options")) { - this.oOptions = oOptions.ui_options; - // textarea - if (this.oOptions && this.oOptions.textarea) { - for (let xNode of document.getElementsByTagName("textarea")) { - if (this._isEligibleNode(xNode) && xNode.getAttribute("spellcheck") !== "false") { - this.lButton.push(new GrammalecteButton(this.nButton, xNode)); - this.nButton += 1; - } - } - } - // editable nodes - if (this.oOptions && this.oOptions.editablenode) { - for (let xNode of document.querySelectorAll("[contenteditable]")) { - if (this._isEligibleNode(xNode)) { - this.lButton.push(new GrammalecteButton(this.nButton, xNode)); - this.nButton += 1; - } - } - } - } - }, - - observePage: function () { - // When a textarea is added via jascript we add the buttons - let that = this; - this.xObserver = new MutationObserver(function (lMutations) { - lMutations.forEach(function (xMutation) { - if (that.oOptions) { - for (let i = 0; i < xMutation.addedNodes.length; i++) { - let xNode = xMutation.addedNodes[i]; - if (xNode.tagName == "TEXTAREA" && that.oOptions.textarea && xNode.getAttribute("spellcheck") !== "false" && that._isEligibleNode(xNode)) { - oGrammalecte.lButton.push(new GrammalecteButton(oGrammalecte.nButton, xNode)); - oGrammalecte.nButton += 1; - } - /*else if (xNode.isContentEditable && that.oOptions.editablenode && that._isEligibleNode(xNode)) { - // this makes Twitter crash when hitting backspace button - oGrammalecte.lButton.push(new GrammalecteButton(oGrammalecte.nButton, xNode)); - oGrammalecte.nButton += 1; - }*/ - else if (xNode.getElementsByTagName && that.oOptions.textarea) { - for (let xSubNode of xNode.getElementsByTagName("textarea")) { - if (that._isEligibleNode(xSubNode) && xSubNode.getAttribute("spellcheck") !== "false") { - oGrammalecte.lButton.push(new GrammalecteButton(oGrammalecte.nButton, xSubNode)); - oGrammalecte.nButton += 1; - } - } - } - /*else if (xNode.querySelectorAll && that.oOptions.editablenode) { - for (let xSubNode of xNode.querySelectorAll("[contenteditable]")) { - if (that._isEligibleNode(xSubNode)) { - oGrammalecte.lButton.push(new GrammalecteButton(oGrammalecte.nButton, xSubNode)); - oGrammalecte.nButton += 1; - } - } - }*/ - } - } - }); - }); - this.xObserver.observe(document.body, { childList: true, subtree: true }); - }, - - rescanPage: function () { - if (this.oTFPanel !== null) { this.oTFPanel.hide(); } - if (this.oGCPanel !== null) { this.oGCPanel.hide(); } - for (let oMenu of this.lButton) { - oMenu.deleteNodes(); - } - this.lButton.length = 0; // to clear an array - this.listenRightClick(); - this.createButtons(); + createButton: function () { + if (this.oPanelButton === null) { + this.oPanelButton = new GrammalecteButton(); + this.oPanelButton.insertIntoPage(); + } }, createTFPanel: function () { if (this.oTFPanel === null) { this.oTFPanel = new GrammalecteTextFormatter("grammalecte_tf_panel", "Formateur de texte", 760, 595, false); @@ -245,11 +173,11 @@ catch (e) { showError(e); } }, - getCaretPosition (xElement) { + getCaretPosition: function (xElement) { // JS awfulness again. // recepie from https://stackoverflow.com/questions/4811822/get-a-ranges-start-and-end-offsets-relative-to-its-parent-container let nCaretOffsetStart = 0; let nCaretOffsetEnd = 0; let xSelection = window.getSelection(); @@ -264,11 +192,11 @@ return [nCaretOffsetStart, nCaretOffsetEnd]; // for later: solution with multilines text // https://stackoverflow.com/questions/4811822/get-a-ranges-start-and-end-offsets-relative-to-its-parent-container/4812022 }, - setCaretPosition (xElement, nCaretOffsetStart, nCaretOffsetEnd) { + setCaretPosition: function (xElement, nCaretOffsetStart, nCaretOffsetEnd) { // JS awfulness again. // recipie from https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div let iChar = 0; let xRange = document.createRange(); xRange.setStart(xElement, 0); @@ -298,10 +226,24 @@ } } let xSelection = window.getSelection(); xSelection.removeAllRanges(); xSelection.addRange(xRange); + }, + + getElementCoord: function (xElem) { + // https://stackoverflow.com/questions/5598743/finding-elements-position-relative-to-the-document + let xBox = xElem.getBoundingClientRect(); + let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + let scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; + let clientTop = document.documentElement.clientTop || document.body.clientTop || 0; + let clientLeft = document.documentElement.clientLeft || document.body.clientLeft || 0; + let top = xBox.top + scrollTop - clientTop; + let left = xBox.left + scrollLeft - clientLeft; + let bottom = xBox.bottom + scrollTop - clientTop; + let right = xBox.right + scrollLeft - clientLeft; + return { top: Math.round(top), left: Math.round(left), bottom: Math.round(bottom), right: Math.round(right) }; } }; function autoRefreshOption (oSavedOptions=null) { @@ -357,11 +299,11 @@ }, parseFull: function (sText) { this.xConnect.postMessage({ sCommand: "parseFull", - oParam: { sText: sTex, sCountry: "FR", bDebug: false, bContext: false }, + oParam: { sText: sText, sCountry: "FR", bDebug: false, bContext: false }, oInfo: {} }); }, getVerb: function (sVerb, bStart=true, bPro=false, bNeg=false, bTpsCo=false, bInt=false, bFem=false) { @@ -391,17 +333,16 @@ /* Messages from the background */ listen: function () { this.xConnect.onMessage.addListener(function (oMessage) { - let {sActionDone, result, oInfo, bEnd, bError} = oMessage; + let { sActionDone, result, oInfo, bEnd, bError } = oMessage; switch (sActionDone) { case "init": oGrammalecte.sExtensionUrl = oMessage.sUrl; - oGrammalecte.listenRightClick(); - oGrammalecte.createButtons(); - oGrammalecte.observePage(); + oGrammalecte.listen(); + oGrammalecte.createButton(); break; case "parseAndSpellcheck": if (oInfo.sDestination == "__GrammalectePanel__") { if (!bEnd) { oGrammalecte.oGCPanel.addParagraphResult(result); Index: gc_lang/fr/webext/content_scripts/menu.css ================================================================== --- gc_lang/fr/webext/content_scripts/menu.css +++ gc_lang/fr/webext/content_scripts/menu.css @@ -1,19 +1,16 @@ /* CSS - Button and menu for Grammalecte + Button for Grammalecte */ -/* - Button -*/ div.grammalecte_menu_main_button { all: initial; position: absolute; box-sizing: border-box; display: none; - margin: -8px 0 0 -8px; + margin: -12px 0 0 -12px; width: 16px; height: 16px; background-color: hsla(210, 80%, 95%, .5); border: 3px solid hsla(210, 80%, 50%, .9); border-top: 3px solid hsla(210, 80%, 90%, .9); @@ -42,104 +39,5 @@ 100% { transform: rotate(360deg) scale(1); box-shadow: 0 0 0 0 hsla(210, 50%, 50%, 0); } } - - -/* - Menu -*/ -div.grammalecte_menu { - all: initial; - display: none; - position: absolute; - margin-left: -10px; - border-radius: 5px; - border: 3px solid hsl(210, 50%, 30%); - box-shadow: 0px 0px 2px hsla(210, 10%, 10%, .5); - background-color: hsl(210, 50%, 30%); - font-family: "Trebuchet MS", "Fira Sans", "Ubuntu Condensed", "Liberation Sans", sans-serif; - z-index: 2147483640; /* maximum is 2147483647: https://stackoverflow.com/questions/491052/minimum-and-maximum-value-of-z-index */ - text-align: left; - width: 220px; -} - -@media print { - .grammalecte_menu { display: none; } -} - -div.grammalecte_menu > div { - line-height: 21px; -} -div.grammalecte_menu > div.grammalecte_menu_close_button { - line-height: 18px; -} - -div.grammalecte_menu_close_button { - float: right; - margin: 2px 2px 0 0; - padding: 1px 5px; - border-radius: 2px; - background-color: hsl(0, 50%, 50%); - color: hsl(0, 20%, 90%); - font-size: 12px; - font-weight: bold; - cursor: pointer; -} -div.grammalecte_menu_close_button:hover { - background-color: hsl(0, 60%, 50%); - color: hsl(0, 30%, 96%); -} - -div.grammalecte_menu_item { - padding: 3px 10px; - background-color: hsl(210, 50%, 40%); - font-size: 14px; - color: hsl(210, 50%, 92%); - cursor: pointer; -} -div.grammalecte_menu_item:hover { - background-color: hsl(210, 50%, 45%); - color: hsl(210, 50%, 100%); -} - -div.grammalecte_menu_item_block { - padding: 3px 10px; - background-color: hsl(210, 50%, 40%); - font-size: 14px; - color: hsl(210, 50%, 92%); - border-top: 1px solid hsl(210, 50%, 30%); - border-radius: 0 0 3px 3px; -} - -div.grammalecte_menu_button { - display: inline-block; - padding: 0 5px; - margin-left: 10px; - border-radius: 2px; - background-color: hsl(210, 50%, 45%); - font-size: 12px; - line-height: 1.6; - text-align: center; - cursor: pointer; -} -div.grammalecte_menu_button:hover { - background-color: hsl(210, 50%, 50%); -} - -div.grammalecte_menu_header { - padding: 2px 10px; - background-color: hsl(210, 50%, 30%); - background-image: url('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'); - background-repeat: no-repeat; - background-position: 10% 50%; - font-size: 16px; - font-family: "Trebuchet MS", "Fira Sans", "Ubuntu Condensed", "Liberation Sans", sans-serif; - color: hsl(210, 50%, 90%); - text-shadow: 0px 0px 2px hsla(210, 10%, 10%, .9); - text-align: center; -} -div.grammalecte_menu_footer { - padding: 2px 10px; - background-color: hsl(210, 50%, 30%); -} Index: gc_lang/fr/webext/content_scripts/menu.js ================================================================== --- gc_lang/fr/webext/content_scripts/menu.js +++ gc_lang/fr/webext/content_scripts/menu.js @@ -4,65 +4,83 @@ /* jslint esversion:6 */ /* global oGrammalecte, showError, window, document */ "use strict"; - class GrammalecteButton { - constructor (nMenu, xNode) { - this.xNode = xNode; - this.xButton = oGrammalecte.createNode("div", {className: "grammalecte_menu_main_button", textContent: " "}); + constructor () { + // the pearl button + this.xButton = oGrammalecte.createNode("div", { className: "grammalecte_menu_main_button", textContent: " " }); this.xButton.onclick = () => { - oGrammalecte.startGCPanel(this.xNode); + if (this.xTextNode) { + oGrammalecte.startGCPanel(this.xTextNode); + } }; - this.xButton.style.zIndex = (xNode.style.zIndex.search(/^[0-9]+$/) !== -1) ? (parseInt(xNode.style.zIndex) + 1).toString() : xNode.style.zIndex; + // about the text node + this.xTextNode = null; + // read user config + this._bTextArea = true; + this._bEditableNode = true; + this._bIframe = false; + if (bChrome) { + browser.storage.local.get("ui_options", this.setOptions.bind(this)); + } else { + browser.storage.local.get("ui_options").then(this.setOptions.bind(this), showError); + } + } + + setOptions (oOptions) { + if (oOptions.hasOwnProperty("ui_options")) { + this._bTextArea = oOptions.ui_options.textarea; + this._bEditableNode = oOptions.ui_options.editablenode; + } + } + + examineNode (xNode) { + if (xNode && xNode instanceof HTMLElement + && ( ((xNode.tagName == "TEXTAREA" || xNode.tagName == "INPUT") && this._bTextArea && xNode.getAttribute("spellcheck") !== "false") + || (xNode.isContentEditable && this._bEditableNode) + || (xNode.tagName == "IFRAME" && this._bIframe) ) + && xNode.style.display !== "none" && xNode.style.visibility !== "hidden" + && !(xNode.dataset.grammalecte_button && xNode.dataset.grammalecte_button == "false")) { + this.xTextNode = xNode; + this.show() + } + else { + this.xTextNode = null; + this.hide(); + } + } + + show () { + if (this.xTextNode) { + this.xButton.style.display = "none"; // we hide it before showing it again to relaunch the animation + let oCoord = oGrammalecte.getElementCoord(this.xTextNode); + //console.log("top:", oCoord.left, "bottom:", oCoord.top, "left:", oCoord.bottom, "right:", oCoord.right); + this.xButton.style.top = `${oCoord.bottom}px`; + this.xButton.style.left = `${oCoord.left}px`; + this.xButton.style.display = "block"; + } + } + + hide () { + this.xButton.style.display = "none"; + } + insertIntoPage () { this.bShadow = document.body.createShadowRoot || document.body.attachShadow; if (this.bShadow) { - this.xShadowBtn = oGrammalecte.createNode("div", {style: "display:none;position:absolute;width:0;height:0;"}); - this.xShadowBtnNode = this.xShadowBtn.attachShadow({mode: "open"}); + this.xShadowBtn = oGrammalecte.createNode("div", { style: "display:none; position:absolute; width:0; height:0;" }); + this.xShadowBtnNode = this.xShadowBtn.attachShadow({ mode: "open" }); oGrammalecte.createStyle("content_scripts/menu.css", null, this.xShadowBtnNode); this.xShadowBtnNode.appendChild(this.xButton); - this._insert(this.xShadowBtn); - } else { + document.body.appendChild(this.xShadowBtnNode); + } + else { if (!document.getElementById("grammalecte_cssmenu")) { oGrammalecte.createStyle("content_scripts/menu.css", "grammalecte_cssmenu", document.head); } - this._insert(this.xButton); - } - this._listen(); - } - - _insert (xNewNode) { - // insertion - let xReferenceNode = this.xNode; - if (this.xNode.classList.contains('rich-editor')) { - xReferenceNode = this.xNode.parentNode; - } - xReferenceNode.parentNode.insertBefore(xNewNode, xReferenceNode.nextSibling); - // offset - let nNodeMarginBottom = parseInt(window.getComputedStyle(this.xNode).marginBottom.replace('px', ''), 10); - let nMarginTop = (this.bShadow) ? -1 * nNodeMarginBottom : -1 * (8 + nNodeMarginBottom); - xNewNode.style.marginTop = nMarginTop + "px"; - } - - _listen () { - this.xNode.addEventListener('focus', (e) => { - if (this.bShadow) { - this.xShadowBtn.style.display = "block"; - } - this.xButton.style.display = "block"; - }); - /*this.xNode.addEventListener('blur', (e) => { - window.setTimeout(() => { this.xButton.style.display = "none"; }, 300); - });*/ - } - - deleteNodes () { - if (this.bShadow) { - this.xShadowBtn.parentNode.removeChild(this.xShadowBtn); - } else { - this.xButton.parentNode.removeChild(this.xButton); + document.body.appendChild(this.xButton); } } }