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 @@ -242,10 +242,65 @@ return xNode; } catch (e) { showError(e); } + }, + + getCaretPosition (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(); + if (xSelection.rangeCount > 0) { + let xRange = xSelection.getRangeAt(0); + let xPreCaretRange = xRange.cloneRange(); + xPreCaretRange.selectNodeContents(xElement); + xPreCaretRange.setEnd(xRange.endContainer, xRange.endOffset); + nCaretOffsetStart = xPreCaretRange.toString().length; + nCaretOffsetEnd = nCaretOffsetStart + xRange.toString().length; + } + 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) { + // 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); + xRange.collapse(true); + + let lNode = [xElement]; + let xNode; + let bFoundStart = false; + let bStop = false; + while (!bStop && (xNode = lNode.pop())) { + if (xNode.nodeType == 3) { // Node.TEXT_NODE + let iNextChar = iChar + xNode.length; + if (!bFoundStart && nCaretOffsetStart >= iChar && nCaretOffsetStart <= iNextChar) { + xRange.setStart(xNode, nCaretOffsetStart - iChar); + bFoundStart = true; + } + if (bFoundStart && nCaretOffsetEnd >= iChar && nCaretOffsetEnd <= iNextChar) { + xRange.setEnd(xNode, nCaretOffsetEnd - iChar); + bStop = true; + } + iChar = iNextChar; + } else { + let i = xNode.childNodes.length; + while (i--) { + lNode.push(xNode.childNodes[i]); + } + } + } + let xSelection = window.getSelection(); + xSelection.removeAllRanges(); + xSelection.addRange(xRange); } }; /* Index: gc_lang/fr/webext/content_scripts/panel_gc.css ================================================================== --- gc_lang/fr/webext/content_scripts/panel_gc.css +++ gc_lang/fr/webext/content_scripts/panel_gc.css @@ -10,11 +10,11 @@ margin: 0 0 5px 0; } p.grammalecte_paragraph { margin: 0; - padding: 10px; + padding: 12px; background-color: hsl(0, 0%, 96%); border-radius: 2px; line-height: normal; text-align: left; font-size: 14px; @@ -48,11 +48,10 @@ background-color: hsl(0, 0%, 40%); color: hsl(0, 0%, 100%); } div.grammalecte_paragraph_actions .grammalecte_green { - width: 80px; background-color: hsl(120, 30%, 50%); color: hsl(0, 0%, 96%); } div.grammalecte_paragraph_actions .grammalecte_green:hover { background-color: hsl(120, 50%, 40%); @@ -64,10 +63,16 @@ } div.grammalecte_paragraph_actions .grammalecte_red:hover { background-color: hsl(0, 50%, 40%); color: hsl(0, 0%, 100%); } + +@keyframes grammalecte-pulse { + 0% { box-shadow: 0 0 0 1px hsla(0, 100%, 50%, .4); } + 50% { box-shadow: 0 0 0 3px hsla(0, 100%, 50%, .4); } + 100% { box-shadow: 0 0 0 1px hsla(0, 100%, 50%, .4); } +} /* TOOLTIP */ Index: gc_lang/fr/webext/content_scripts/panel_gc.js ================================================================== --- gc_lang/fr/webext/content_scripts/panel_gc.js +++ gc_lang/fr/webext/content_scripts/panel_gc.js @@ -86,16 +86,22 @@ try { if (oResult && (oResult.sParagraph.trim() !== "" || oResult.aGrammErr.length > 0 || oResult.aSpellErr.length > 0)) { let xNodeDiv = oGrammalecte.createNode("div", {className: "grammalecte_paragraph_block"}); // actions let xActionsBar = oGrammalecte.createNode("div", {className: "grammalecte_paragraph_actions"}); - xActionsBar.appendChild(oGrammalecte.createNode("div", {id: "grammalecte_check" + oResult.iParaNum, className: "grammalecte_paragraph_button grammalecte_green", textContent: "Réanalyser"}, {para_num: oResult.iParaNum})); - xActionsBar.appendChild(oGrammalecte.createNode("div", {id: "grammalecte_hide" + oResult.iParaNum, className: "grammalecte_paragraph_button grammalecte_red", textContent: "×", style: "font-weight: bold;"})); + xActionsBar.appendChild(oGrammalecte.createNode("div", {id: "grammalecte_check" + oResult.iParaNum, className: "grammalecte_paragraph_button grammalecte_green", textContent: "A", title: "Réanalyser…"}, {para_num: oResult.iParaNum})); + xActionsBar.appendChild(oGrammalecte.createNode("div", {id: "grammalecte_hide" + oResult.iParaNum, className: "grammalecte_paragraph_button grammalecte_red", textContent: "×", title: "Cacher", style: "font-weight: bold;"})); // paragraph let xParagraph = oGrammalecte.createNode("p", {id: "grammalecte_paragraph"+oResult.iParaNum, className: "grammalecte_paragraph", lang: "fr", contentEditable: "true"}, {para_num: oResult.iParaNum}); xParagraph.setAttribute("spellcheck", "false"); // doesn’t seem possible to use “spellcheck” as a common attribute. - xParagraph.addEventListener("keyup", function (xEvent) { + xParagraph.dataset.timer_id = "0"; + xParagraph.addEventListener("input", function (xEvent) { + window.clearTimeout(parseInt(xParagraph.dataset.timer_id)); + xParagraph.dataset.timer_id = window.setTimeout(this.recheckParagraph.bind(this), 3000, oResult.iParaNum); + let [nStart, nEnd] = oGrammalecte.getCaretPosition(xParagraph); + xParagraph.dataset.caret_position_start = nStart; + xParagraph.dataset.caret_position_end = nEnd; this.oNodeControl.setParagraph(parseInt(xEvent.target.dataset.para_num), this.purgeText(xEvent.target.textContent)); this.oNodeControl.write(); }.bind(this) , true); this._tagParagraph(xParagraph, oResult.sParagraph, oResult.iParaNum, oResult.aGrammErr, oResult.aSpellErr); @@ -197,20 +203,26 @@ return xNodeErr; } blockParagraph (xParagraph) { xParagraph.contentEditable = "false"; - this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).textContent = "Analyse…"; + this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).textContent = "!!"; this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).style.backgroundColor = "hsl(0, 50%, 50%)"; - this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).style.boxShadow = "0 0 0 3px hsla(0, 100%, 50%, .2)"; + this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).style.boxShadow = "0 0 0 3px hsla(0, 0%, 50%, .2)"; + this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).style.animation = "grammalecte-pulse 1s linear infinite"; + } freeParagraph (xParagraph) { xParagraph.contentEditable = "true"; - this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).textContent = "Réanalyser"; + let nStart = parseInt(xParagraph.dataset.caret_position_start); + let nEnd = parseInt(xParagraph.dataset.caret_position_end); + oGrammalecte.setCaretPosition(xParagraph, nStart, nEnd); + this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).textContent = "A"; this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).style.backgroundColor = "hsl(120, 30%, 50%)"; - this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).style.boxShadow = "none"; + this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).style.animation = ""; + setTimeout(() => { this.xParent.getElementById("grammalecte_check"+xParagraph.dataset.para_num).style.boxShadow = ""; }, 1000); } applySuggestion (sNodeSuggId) { // sugg try { let sErrorId = this.xParent.getElementById(sNodeSuggId).dataset.error_id; @@ -259,11 +271,10 @@ document.removeEventListener("copy", setClipboardData, true); xEvent.stopImmediatePropagation(); xEvent.preventDefault(); xEvent.clipboardData.setData("text/plain", sText); } - document.addEventListener("copy", setClipboardData, true); document.execCommand("copy"); } copyTextToClipboard () {