bundled/include/LibreOfficeKit/LibreOfficeKit.h | 6 bundled/include/LibreOfficeKit/LibreOfficeKit.hxx | 13 kit/ChildSession.cpp | 28 kit/ChildSession.hpp | 1 loleaflet/build/deps.js | 7 loleaflet/src/control/Control.LokDialog.js | 120 --- loleaflet/src/control/Control.MobileInput.js | 343 ----------- loleaflet/src/control/Control.Toolbar.js | 2 loleaflet/src/core/Browser.js | 4 loleaflet/src/core/Socket.js | 7 loleaflet/src/layer/marker/ClipboardContainer.js | 666 ++++++++++++++++++---- loleaflet/src/layer/tile/TileLayer.js | 63 +- loleaflet/src/map/Map.js | 81 ++ loleaflet/src/map/handler/Map.Keyboard.js | 229 +++---- loleaflet/src/map/handler/Map.TouchGesture.js | 5 loolwsd.xml.in | 2 wsd/ClientSession.cpp | 3 wsd/LOOLWSD.cpp | 2 18 files changed, 854 insertions(+), 728 deletions(-)
New commits: commit 974d02fcca70d96cb09b71fc1e33c0950ae50b29 Author: Henry Castro <[email protected]> AuthorDate: Wed Aug 28 21:26:46 2019 -0400 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: mobile: fix the first typed character after opening the document calling the function setSelectionRange is an implicit keyboard focus. Only enable when the text area has the focus Change-Id: Ic58abd3fc555ad9a0a08a01041f7aeb5367d271b diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 643e00567..4d996ffb8 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -507,7 +507,7 @@ L.ClipboardContainer = L.Layer.extend({ // always catch deleteContentBackward/deleteContentForward input events // (some combination of browser + input method don't fire those on an // empty contenteditable). - _emptyArea: function _emptyArea() { + _emptyArea: function _emptyArea(noSelect) { this._fancyLog('empty-area'); this._ignoreInputCount++; @@ -524,7 +524,7 @@ L.ClipboardContainer = L.Layer.extend({ this._textArea.value = this._preSpaceChar + this._postSpaceChar; // avoid setting the focus keyboard - if (document.activeElement === this._textArea) { + if (!noSelect) { this._textArea.setSelectionRange(1, 1); if (this._hasWorkingSelectionStart === undefined) @@ -566,7 +566,7 @@ L.ClipboardContainer = L.Layer.extend({ this._fancyLog('abort-composition', ev.type); if (this._isComposing) this._isComposing = false; - this._emptyArea(); + this._emptyArea(document.activeElement !== this._textArea); }, _onKeyDown: function _onKeyDown(ev) { commit e33fc028766ab3d3d321019aa33d09debfa38009 Author: Henry Castro <[email protected]> AuthorDate: Mon Aug 19 07:52:33 2019 -0400 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: mobile: removes keyboard focus when the graphic is selected Change-Id: Iced49475ebf9af5508059f5d6e223e99d1187649 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index e7f7a2ed7..643e00567 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -522,9 +522,14 @@ L.ClipboardContainer = L.Layer.extend({ this._lastContent = []; this._textArea.value = this._preSpaceChar + this._postSpaceChar; - this._textArea.setSelectionRange(1, 1); - if (this._hasWorkingSelectionStart === undefined) - this._hasWorkingSelectionStart = (this._textArea.selectionStart === 1); + + // avoid setting the focus keyboard + if (document.activeElement === this._textArea) { + this._textArea.setSelectionRange(1, 1); + + if (this._hasWorkingSelectionStart === undefined) + this._hasWorkingSelectionStart = (this._textArea.selectionStart === 1); + } this._fancyLog('empty-area-end'); diff --git a/loleaflet/src/map/handler/Map.TouchGesture.js b/loleaflet/src/map/handler/Map.TouchGesture.js index fcdab2bd4..ae35507fa 100644 --- a/loleaflet/src/map/handler/Map.TouchGesture.js +++ b/loleaflet/src/map/handler/Map.TouchGesture.js @@ -239,7 +239,9 @@ L.Map.TouchGesture = L.Handler.extend({ this._map._docLayer._postMouseEvent('buttondown', mousePos.x, mousePos.y, 1, 1, 0); this._map._docLayer._postMouseEvent('buttonup', mousePos.x, mousePos.y, 1, 1, 0); - if (!this._map.hasFocus()) { + if (this._state === L.Map.TouchGesture.MARKER || this._state === L.Map.TouchGesture.GRAPHIC) { + this._map._clipboardContainer.blur(); + } else { this._map.focus(); } }, commit 1240da475fae851668a8103f2461910d99a829ce Author: Henry Castro <[email protected]> AuthorDate: Thu Aug 15 16:29:24 2019 -0400 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: mobile: hide the cursor marker when exists text selection Change-Id: Ib0a5c74567e1a0a71c53d741aa6c44a09b6b0fe2 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index ee90597d1..e7f7a2ed7 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -242,7 +242,11 @@ L.ClipboardContainer = L.Layer.extend({ // Move and display under-caret marker if (L.Browser.touch) { - this._cursorHandler.setLatLng(bottom).addTo(this._map); + if (this._map._docLayer._selections.getLayers().length === 0) { + this._cursorHandler.setLatLng(bottom).addTo(this._map); + } else { + this._map.removeLayer(this._cursorHandler); + } } // Move the hidden text area with the cursor commit e39fb629b48a4675608103d87ea9ba3af008337b Author: Michael Meeks <[email protected]> AuthorDate: Thu Oct 3 14:19:59 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Remove obsolete and unhelpful method. Fixes up 8440e286c merge. Change-Id: I27f16d36d135feae10de6d1db732259f81afd1fc diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 7239126e3..ee90597d1 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -157,17 +157,6 @@ L.ClipboardContainer = L.Layer.extend({ return arr; }, - setValue: function(val) { - // console.log('clipboard setValue: ', val); - if (this._legacyArea) { - var tmp = document.createElement('div'); - tmp.innerHTML = val; - this._textArea.value = tmp.innerText || tmp.textContent || ''; - } else { - this._textArea.innerHTML = val; - } - }, - update: function() { if (this._container && this._map && this._latlng) { var position = this._map.latLngToLayerPoint(this._latlng).round(); diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index dd923df61..db2da511d 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -655,10 +655,6 @@ L.TileLayer = L.GridLayer.extend({ // messages during text composition, and resetting the contents // of the clipboard container mid-composition will easily break it. var formula = textMsg.substring(13); - if (!this._map['wopi'].DisableCopy) { - this._map._clipboardContainer.setValue(formula); - this._map._clipboardContainer.select(); - } this._lastFormula = formula; this._map.fire('cellformula', {formula: formula}); }, commit 2210fddb2d89ed96ed425671423fdef1eebbf6a0 Author: Michael Meeks <[email protected]> AuthorDate: Thu Oct 3 14:09:03 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Remove unused code. Change-Id: I7d75cd570411a3e9b596b853da9ebc77b703ee03 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 17c420397..7239126e3 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -131,16 +131,6 @@ L.ClipboardContainer = L.Layer.extend({ this._textArea.select(); }, - warnCopyPaste: function() { - var self = this; - vex.dialog.alert({ - unsafeMessage: _('<p>Your browser has very limited access to the clipboard, so use these keyboard shortcuts:<ul><li><b>Ctrl+C</b>: For copying.</li><li><b>Ctrl+X</b>: For cutting.</li><li><b>Ctrl+V</b>: For pasting.</li></ul></p>'), - callback: function () { - self._map.focus(); - } - }); - }, - getValue: function() { var value = this._textArea.value; return value; commit 3783e29db574a9e4e2c49e0bf3db581301603fc2 Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Mon Jul 29 17:46:42 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: preventDefault() on lokDialog clicks to avoid focus changes. Change-Id: I95c3f94562cbfd0de71cd7330fa0b1baf1562a21 diff --git a/loleaflet/src/control/Control.LokDialog.js b/loleaflet/src/control/Control.LokDialog.js index c41f0772f..b00ff806d 100644 --- a/loleaflet/src/control/Control.LokDialog.js +++ b/loleaflet/src/control/Control.LokDialog.js @@ -615,7 +615,7 @@ L.Control.LokDialog = L.Control.extend({ }, this); L.DomEvent.on(canvas, 'mousedown mouseup', function(e) { - L.DomEvent.stopPropagation(e); + L.DomEvent.stop(e); var buttons = 0; if (this._map['mouse']) { buttons |= e.button === this._map['mouse'].JSButtons.left ? this._map['mouse'].LOButtons.left : 0; @@ -629,6 +629,12 @@ L.Control.LokDialog = L.Control.extend({ this._postWindowMouseEvent(lokEventType, id, e.offsetX, e.offsetY, 1, buttons, 0); //dlgInput.focus(); }, this); + + L.DomEvent.on(canvas, 'click', function(ev) { + // Clicking on the dialog's canvas shall not trigger any + // focus change - therefore the event is stopped and preventDefault()ed. + L.DomEvent.stop(ev); + }); }, _setupGestures: function(dialogContainer, id, canvas) { commit 917c357d7e3009ed42876761119467b863471462 Author: Marco Cecchetti <[email protected]> AuthorDate: Tue Jul 23 21:50:55 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 android-chrome: keyboard disappears when tapping on current input field Change-Id: I76145a4c4eeba6f1cb9ae3f05784d426ea298ccc diff --git a/loleaflet/src/control/Control.LokDialog.js b/loleaflet/src/control/Control.LokDialog.js index b7a842ef4..c41f0772f 100644 --- a/loleaflet/src/control/Control.LokDialog.js +++ b/loleaflet/src/control/Control.LokDialog.js @@ -386,6 +386,7 @@ L.Control.LokDialog = L.Control.extend({ // set the position of the cursor container element L.DomUtil.setStyle(this._dialogs[dlgId].cursor, 'left', x + 'px'); L.DomUtil.setStyle(this._dialogs[dlgId].cursor, 'top', y + 'px'); + this._map.getClipboardContainer().focus(); }, _createDialogCursor: function(dialogId) { commit 51779f9f002bfe4645afebc31227e8d8e2bf297c Author: Michael Meeks <[email protected]> AuthorDate: Tue Jul 23 20:41:16 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 input: use pre-pended non-blanking space & handle GBoard better. GBoard's unwelcome & unwanted movement of our cursor does not necessarily mean backspace - so special case detect that. Also use pre-pended on Android, apparently it stops GBoard capitalizing everything, hmm. Change-Id: Idfbc696c3e45f173895ed0f26abaea3a32ad86c0 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 20e20cb65..17c420397 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -25,8 +25,13 @@ L.ClipboardContainer = L.Layer.extend({ this._hasWorkingSelectionStart = undefined; // does it work ? this._ignoreNextBackspace = false; + this._preSpaceChar = ' '; // Might need to be \xa0 in some legacy browsers ? - this._spaceChar = ' '; + if (L.Browser.android && L.Browser.webkit) { + // fool GBoard into not auto-capitalizing constantly + this._preSpaceChar = '\xa0'; + } + this._postSpaceChar = ' '; // Debug flag, used in fancyLog(). See the debug() method. // this._isDebugOn = true; @@ -358,19 +363,23 @@ L.ClipboardContainer = L.Layer.extend({ // Backspaces and deletes at the beginning / end are filtered out, so // we get a beforeinput, but no input for them. Sometimes we can end up // in a state where we lost our leading / terminal chars and can't recover - _onBeforeInput: function _onBeforeInput(/* ev */) { + _onBeforeInput: function _onBeforeInput(ev) { this._ignoreNextBackspace = false; if (this._hasWorkingSelectionStart) { var value = this._textArea.value; - if (value.length == 2 && value === this._spaceChar + this._spaceChar && + if (value.length == 2 && value === this._preSpaceChar + this._postSpaceChar && this._textArea.selectionStart === 0) { // It seems some inputs eg. GBoard can magically move the cursor from " | " to "| " console.log('Oh dear, gboard sabotaged our cursor position, fixing'); - this._removeTextContent(1, 0); + // But when we detect the problem only emit a delete when we have one. + if (ev.inputType && ev.inputType === 'deleteContentBackward') + { + this._removeTextContent(1, 0); + // Having mended it we now get a real backspace on input (sometimes) + this._ignoreNextBackspace = true; + } this._emptyArea(); - // Having mended it we now get a real backspace on input (sometimes) - this._ignoreNextBackspace = true; } } }, @@ -393,19 +402,24 @@ L.ClipboardContainer = L.Layer.extend({ this._deleteHint = ''; } + var ignoreBackspace = this._ignoreNextBackspace; + this._ignoreNextBackspace = false; + var content = this.getValueAsCodePoints(); - var spaceChar = this._spaceChar.charCodeAt(0); + var preSpaceChar = this._preSpaceChar.charCodeAt(0); + var postSpaceChar = this._postSpaceChar.charCodeAt(0); // We use a different leading and terminal space character // to differentiate backspace from delete, then replace the character. - if (content.length < 1 || content[0] !== spaceChar) { // missing initial space + if (content.length < 1 || content[0] !== preSpaceChar) { // missing initial space console.log('Sending backspace'); - this._removeTextContent(1, 0); + if (!ignoreBackspace) + this._removeTextContent(1, 0); this._emptyArea(); return; } - if (content[content.length-1] !== spaceChar) { // missing trailing space. + if (content[content.length-1] !== postSpaceChar) { // missing trailing space. console.log('Sending delete'); this._removeTextContent(0, 1); this._emptyArea(); @@ -416,9 +430,8 @@ L.ClipboardContainer = L.Layer.extend({ if (this._deleteHint == 'backspace' || this._textArea.selectionStart === 0) { - if (!this._ignoreNextBackspace) + if (!ignoreBackspace) this._removeTextContent(1, 0); - this._ignoreNextBackspace = false; } else if (this._deleteHint == 'delete' || this._textArea.selectionStart === 1) @@ -525,7 +538,7 @@ L.ClipboardContainer = L.Layer.extend({ console.log('Set old/lastContent to empty'); this._lastContent = []; - this._textArea.value = this._spaceChar + this._spaceChar; + this._textArea.value = this._preSpaceChar + this._postSpaceChar; this._textArea.setSelectionRange(1, 1); if (this._hasWorkingSelectionStart === undefined) this._hasWorkingSelectionStart = (this._textArea.selectionStart === 1); commit f850d01d2641d3ed769367619f7608f44265e2bc Author: Michael Meeks <[email protected]> AuthorDate: Tue Jul 23 18:19:40 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 input: remove Gecko special-case breaking new-line input. Seems we can unify this, and let the textarea handle enters. Change-Id: I4ff020993ba562fb95899c3ff113dfc835f7b419 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index b589683f3..20e20cb65 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -481,14 +481,10 @@ L.ClipboardContainer = L.Layer.extend({ // MSIE/Edge cannot compare a string to "\n" for whatever reason, // so compare charcode as well if (text === '\n' || (text.length === 1 && text.charCodeAt(0) === 13)) { - // we get a duplicate key-event on Gecko, oddly so drop it. - if (!L.Browser.gecko) - { - // The composition messages doesn't play well with just a line break, - // therefore send a keystroke. - this._sendKeyEvent(13, 1280); - this._emptyArea(); - } + // The composition messages doesn't play well with just a line break, + // therefore send a keystroke. + this._sendKeyEvent(13, 1280); + this._emptyArea(); } else { // The composition messages doesn't play well with line breaks inside // the composed word (e.g. word and a newline are queued client-side diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js index 1e5299ef7..fb0f8954f 100644 --- a/loleaflet/src/map/handler/Map.Keyboard.js +++ b/loleaflet/src/map/handler/Map.Keyboard.js @@ -338,10 +338,10 @@ L.Map.Keyboard = L.Handler.extend({ } } else if ((ev.type === 'keypress') && (!this.handleOnKeyDownKeys[keyCode] || charCode !== 0)) { - if (keyCode === 8 || keyCode === 46) + if (keyCode === 8 || keyCode === 46 || keyCode === 13) { // handled generically in ClipboardContainer.js - console.log('Ignore backspace/delete keypress'); + console.log('Ignore backspace/delete/enter keypress'); return; } if (charCode === keyCode && charCode !== 13) { commit a78551ce02cfa67eab2b5baa6b4c6c6eaecd9301 Author: Michael Meeks <[email protected]> AuthorDate: Tue Jul 23 17:59:43 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 input: ensure we emit a backspace as we repair for gboard. But don't let ourselves emit another one with in an input call later. Change-Id: Ic4443b5e6d5a5dbb1ac5381328134af98739b299 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 631456867..b589683f3 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -23,6 +23,7 @@ L.ClipboardContainer = L.Layer.extend({ // Content this._lastContent = []; // unicode characters this._hasWorkingSelectionStart = undefined; // does it work ? + this._ignoreNextBackspace = false; // Might need to be \xa0 in some legacy browsers ? this._spaceChar = ' '; @@ -310,6 +311,7 @@ L.ClipboardContainer = L.Layer.extend({ if (this._isDebugOn) { var state = this._isComposing ? 'C' : 'N'; state += this._hasWorkingSelectionStart ? 'S' : '-'; + state += this._ignoreNextBackspace ? 'I' : '-'; state += ' '; var textSel = this._textArea.selectionStart + '!' + this._textArea.selectionEnd; @@ -357,14 +359,18 @@ L.ClipboardContainer = L.Layer.extend({ // we get a beforeinput, but no input for them. Sometimes we can end up // in a state where we lost our leading / terminal chars and can't recover _onBeforeInput: function _onBeforeInput(/* ev */) { + this._ignoreNextBackspace = false; if (this._hasWorkingSelectionStart) { - if (this._textArea.length == 2 && - this._textArea.value === this._spaceChar + this._spaceChar && + var value = this._textArea.value; + if (value.length == 2 && value === this._spaceChar + this._spaceChar && this._textArea.selectionStart === 0) { // It seems some inputs eg. GBoard can magically move the cursor from " | " to "| " console.log('Oh dear, gboard sabotaged our cursor position, fixing'); + this._removeTextContent(1, 0); this._emptyArea(); + // Having mended it we now get a real backspace on input (sometimes) + this._ignoreNextBackspace = true; } } }, @@ -409,7 +415,11 @@ L.ClipboardContainer = L.Layer.extend({ console.log('Missing terminal nodes: ' + this._deleteHint); if (this._deleteHint == 'backspace' || this._textArea.selectionStart === 0) - this._removeTextContent(1, 0); + { + if (!this._ignoreNextBackspace) + this._removeTextContent(1, 0); + this._ignoreNextBackspace = false; + } else if (this._deleteHint == 'delete' || this._textArea.selectionStart === 1) this._removeTextContent(0, 1); commit 103a039fd21d27222d6a9c66e8eff33fc157820c Author: Michael Meeks <[email protected]> AuthorDate: Tue Jul 23 17:35:50 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 input: track if we have a working selectionStart & correct problems. It -seems- that GBoard can move the cursor selection in some cases. Adding the test code to fetch the cursor position seems to avoid it being called, possibly timing sensitive. Change-Id: I97a3bceec2169e8c15b8157a3c3eca1005a69172 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index f9f5ccade..631456867 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -22,7 +22,7 @@ L.ClipboardContainer = L.Layer.extend({ // Content this._lastContent = []; // unicode characters - this._lastCursor = 1; // last cursor position. + this._hasWorkingSelectionStart = undefined; // does it work ? // Might need to be \xa0 in some legacy browsers ? this._spaceChar = ' '; @@ -91,6 +91,7 @@ L.ClipboardContainer = L.Layer.extend({ ); onoff(this._textArea, 'input', this._onInput, this); + onoff(this._textArea, 'beforeinput', this._onBeforeInput, this); onoff(this._textArea, 'compositionstart', this._onCompositionStart, this); onoff(this._textArea, 'compositionupdate', this._onCompositionUpdate, this); onoff(this._textArea, 'compositionend', this._onCompositionEnd, this); @@ -308,6 +309,7 @@ L.ClipboardContainer = L.Layer.extend({ // Pretty-print on console (but only if "tile layer debug mode" is active) if (this._isDebugOn) { var state = this._isComposing ? 'C' : 'N'; + state += this._hasWorkingSelectionStart ? 'S' : '-'; state += ' '; var textSel = this._textArea.selectionStart + '!' + this._textArea.selectionEnd; @@ -351,6 +353,22 @@ L.ClipboardContainer = L.Layer.extend({ } }, + // Backspaces and deletes at the beginning / end are filtered out, so + // we get a beforeinput, but no input for them. Sometimes we can end up + // in a state where we lost our leading / terminal chars and can't recover + _onBeforeInput: function _onBeforeInput(/* ev */) { + if (this._hasWorkingSelectionStart) { + if (this._textArea.length == 2 && + this._textArea.value === this._spaceChar + this._spaceChar && + this._textArea.selectionStart === 0) + { + // It seems some inputs eg. GBoard can magically move the cursor from " | " to "| " + console.log('Oh dear, gboard sabotaged our cursor position, fixing'); + this._emptyArea(); + } + } + }, + // Fired when text has been inputed, *during* and after composing/spellchecking _onInput: function _onInput(ev) { this._map.notifyActive(); @@ -436,7 +454,6 @@ L.ClipboardContainer = L.Layer.extend({ newText = newText.slice(matchTo); this._lastContent = content; - this._lastCursor = this._textArea.selectionStart; if (newText.length > 0) this._sendText(String.fromCharCode.apply(null, newText)); @@ -503,9 +520,11 @@ L.ClipboardContainer = L.Layer.extend({ this._lastContent = []; this._textArea.value = this._spaceChar + this._spaceChar; - /// TODO: Check that this selection method works with MSIE11 this._textArea.setSelectionRange(1, 1); - this._lastCursor = 1; + if (this._hasWorkingSelectionStart === undefined) + this._hasWorkingSelectionStart = (this._textArea.selectionStart === 1); + + this._fancyLog('empty-area-end'); this._ignoreInputCount--; }, commit f68d56735c280923a17b7ba972876e2976cada0a Author: Michael Meeks <[email protected]> AuthorDate: Tue Jul 23 15:43:41 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 input: handle backspace on android/gboard more robustly. We get no direction hint in this case - so use cursor position. Change-Id: Ic69f075507fe619ccac84f3d5a595bf6cd413a41 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index ed6f3bbbc..f9f5ccade 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -389,10 +389,12 @@ L.ClipboardContainer = L.Layer.extend({ } if (content.length < 2) { console.log('Missing terminal nodes: ' + this._deleteHint); - if (this._deleteHint == 'delete') - this._removeTextContent(0, 1); - else if (this._deleteHint == 'backspace') + if (this._deleteHint == 'backspace' || + this._textArea.selectionStart === 0) this._removeTextContent(1, 0); + else if (this._deleteHint == 'delete' || + this._textArea.selectionStart === 1) + this._removeTextContent(0, 1); else console.log('Cant detect delete or backspace'); this._emptyArea(); commit 67eafba6b7a5ed06acea69da94e1df5bb933b654 Author: Michael Meeks <[email protected]> AuthorDate: Mon Jul 22 20:46:46 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Enable firefox, IE11, Edge, etc. Ignore backspace keypress to avoid duplicate deletion in firefox. Handle distinguishing <space><delete> from <backspace>. Change-Id: Ia279aab929977b3522452adcbfac0c4aad189771 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 1d9cbcaba..ed6f3bbbc 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -13,11 +13,19 @@ L.ClipboardContainer = L.Layer.extend({ // compositionstart/compositionend events; unused this._isComposing = false; + // We need to detect whether delete or backspace was + // pressed sometimes - consider ' foo' -> ' foo' + this._deleteHint = ''; // or 'delete' or 'backspace' + // Clearing the area can generate input events this._ignoreInputCount = 0; // Content this._lastContent = []; // unicode characters + this._lastCursor = 1; // last cursor position. + + // Might need to be \xa0 in some legacy browsers ? + this._spaceChar = ' '; // Debug flag, used in fancyLog(). See the debug() method. // this._isDebugOn = true; @@ -86,6 +94,7 @@ L.ClipboardContainer = L.Layer.extend({ onoff(this._textArea, 'compositionstart', this._onCompositionStart, this); onoff(this._textArea, 'compositionupdate', this._onCompositionUpdate, this); onoff(this._textArea, 'compositionend', this._onCompositionEnd, this); + onoff(this._textArea, 'keydown', this._onKeyDown, this); onoff(this._textArea, 'keyup', this._onKeyUp, this); onoff(this._textArea, 'copy cut paste', this._map._handleDOMEvent, this._map); @@ -127,8 +136,6 @@ L.ClipboardContainer = L.Layer.extend({ getValue: function() { var value = this._textArea.value; - // kill unwanted entities - value = value.replace(/ /g, ' '); return value; }, @@ -303,6 +310,9 @@ L.ClipboardContainer = L.Layer.extend({ var state = this._isComposing ? 'C' : 'N'; state += ' '; + var textSel = this._textArea.selectionStart + '!' + this._textArea.selectionEnd; + state += textSel + ' '; + var sel = window.getSelection(); var content = this.getValue(); if (sel === null) @@ -328,6 +338,8 @@ L.ClipboardContainer = L.Layer.extend({ content = content.slice(0, cursorPos) + '|' + content.slice(cursorPos); } + state += '[' + this._deleteHint + '] '; + console.log2( + new Date() + ' %cINPUT%c: ' + state + '"' + content + '" ' + type + '%c ', @@ -340,7 +352,7 @@ L.ClipboardContainer = L.Layer.extend({ }, // Fired when text has been inputed, *during* and after composing/spellchecking - _onInput: function _onInput(/* ev */) { + _onInput: function _onInput(ev) { this._map.notifyActive(); if (this._ignoreInputCount > 0) { @@ -348,22 +360,44 @@ L.ClipboardContainer = L.Layer.extend({ return; } + if (ev.inputType) { + if (ev.inputType == 'deleteContentForward') + this._deleteHint = 'delete'; + else if (ev.inputType == 'deleteContentBackward') + this._deleteHint = 'backspace'; + else + this._deleteHint = ''; + } + var content = this.getValueAsCodePoints(); + var spaceChar = this._spaceChar.charCodeAt(0); + // We use a different leading and terminal space character // to differentiate backspace from delete, then replace the character. - if (content[0] !== 16*10) { // missing initial non-breaking space. + if (content.length < 1 || content[0] !== spaceChar) { // missing initial space console.log('Sending backspace'); this._removeTextContent(1, 0); this._emptyArea(); return; } - if (content[content.length-1] !== 32) { // missing trailing space. + if (content[content.length-1] !== spaceChar) { // missing trailing space. console.log('Sending delete'); this._removeTextContent(0, 1); this._emptyArea(); return; } + if (content.length < 2) { + console.log('Missing terminal nodes: ' + this._deleteHint); + if (this._deleteHint == 'delete') + this._removeTextContent(0, 1); + else if (this._deleteHint == 'backspace') + this._removeTextContent(1, 0); + else + console.log('Cant detect delete or backspace'); + this._emptyArea(); + return; + } // remove leading & tailing spaces. content = content.slice(1, -1); @@ -377,18 +411,37 @@ L.ClipboardContainer = L.Layer.extend({ '\tnew "' + String.fromCharCode.apply(null, content) + '" (' + content.length + ')' + '\n' + '\told "' + String.fromCharCode.apply(null, this._lastContent) + '" (' + this._lastContent.length + ')'); - var remove = this._lastContent.length - matchTo; - if (remove > 0) - this._removeTextContent(remove, 0); + var removeBefore = this._lastContent.length - matchTo; + var removeAfter = 0; + + if (this._lastContent.length > content.length) + { + // Pressing '<space><delete>' can delete our terminal space + // such that subsequent deletes will do nothing; need to + // detect and reset in this case. + if (this._deleteHint === 'delete') + { + removeBefore--; + removeAfter++; + } + } + + if (removeBefore > 0 || removeAfter > 0) + this._removeTextContent(removeBefore, removeAfter); var newText = content; if (matchTo > 0) newText = newText.slice(matchTo); this._lastContent = content; + this._lastCursor = this._textArea.selectionStart; if (newText.length > 0) this._sendText(String.fromCharCode.apply(null, newText)); + + // was a 'delete' and we need to reset world. + if (removeAfter > 0) + this._emptyArea(); }, // Sends the given (UTF-8) string of text to lowsd, as IME (text composition) @@ -439,15 +492,18 @@ L.ClipboardContainer = L.Layer.extend({ this._ignoreInputCount++; // Note: 0xA0 is 160, which is the character code for non-breaking space: // https://www.fileformat.info/info/unicode/char/00a0/index.htm + // Using normal spaces would make FFX/Gecko collapse them into an // empty string. + // FIXME: is that true !? ... console.log('Set old/lastContent to empty'); this._lastContent = []; - this._textArea.value = '\xa0 '; + this._textArea.value = this._spaceChar + this._spaceChar; /// TODO: Check that this selection method works with MSIE11 this._textArea.setSelectionRange(1, 1); + this._lastCursor = 1; this._ignoreInputCount--; }, @@ -485,66 +541,13 @@ L.ClipboardContainer = L.Layer.extend({ this._emptyArea(); }, - // Override the system default for pasting into the textarea/contenteditable, - // and paste into the document instead. - _onPaste: function _onPaste(ev) { - // Prevent the event's default - in this case, prevent the clipboard contents - // from being added to the hidden textarea and firing 'input'/'textInput' events. - ev.preventDefault(); - - // TODO: handle internal selection here (compare pasted plaintext with the - // last copied/cut plaintext, send a UNO 'paste' command over websockets if so. - // if (this._lastClipboardText === ...etc... - - var pasteString; - if (ev.clipboardData) { - pasteString = ev.clipboardData.getData('text/plain'); // non-IE11 - } else if (window.clipboardData) { - pasteString = window.clipboardData.getData('Text'); // IE 11 - } - - if (pasteString && pasteString === this._lastClipboardText) { - // If the pasted text is the same as the last copied/cut text, - // let lowsd use LOK's clipboard instead. This is done in order - // to keep formatting and non-text bits. - this._map._socket.sendMessage('uno .uno:Paste'); - return; - } - - // Let the TileLayer functionality take care of sending the - // DataTransfer from the event to lowsd. - this._map._docLayer._dataTransferToDocument( - ev.clipboardData || window.clipboardData /* IE11 */ - ); - - this._abortComposition(); - }, - - // Override the system default for cut & copy - ensure that the system clipboard - // receives *plain text* (instead of HTML/RTF), and save internal state. - // TODO: Change the 'gettextselection' command, so that it can fetch the HTML - // version of the copied text **maintaining typefaces**. - _onCutCopy: function _onCutCopy(ev) { - var plaintext = document.getSelection().toString(); - - this._lastClipboardText = plaintext; - - if (ev.type === 'copy') { - this._map._socket.sendMessage('uno .uno:Copy'); - } else if (ev.type === 'cut') { - this._map._socket.sendMessage('uno .uno:Cut'); - } - - if (event.clipboardData) { - event.clipboardData.setData('text/plain', plaintext); // non-IE11 - } else if (window.clipboardData) { - window.clipboardData.setData('Text', plaintext); // IE 11 - } else { - console.warn('Could not set the clipboard contents to plain text.'); - return; - } - - event.preventDefault(); + _onKeyDown: function _onKeyDown(ev) { + if (ev.keyCode == 8) + this._deleteHint = 'backspace'; + else if (ev.keyCode == 46) + this._deleteHint = 'delete'; + else + this._deleteHint = ''; }, // Check arrow keys on 'keyup' event; using 'ArrowLeft' or 'ArrowRight' diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js index 0ef5d1a66..1e5299ef7 100644 --- a/loleaflet/src/map/handler/Map.Keyboard.js +++ b/loleaflet/src/map/handler/Map.Keyboard.js @@ -338,6 +338,12 @@ L.Map.Keyboard = L.Handler.extend({ } } else if ((ev.type === 'keypress') && (!this.handleOnKeyDownKeys[keyCode] || charCode !== 0)) { + if (keyCode === 8 || keyCode === 46) + { + // handled generically in ClipboardContainer.js + console.log('Ignore backspace/delete keypress'); + return; + } if (charCode === keyCode && charCode !== 13) { // Chrome sets keyCode = charCode for printable keys // while LO requires it to be 0 commit 73839f0dffe4c9c31c782cf3a634e0b824446a47 Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Mon Jul 22 17:06:55 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Prevent automatic line breaks in the textarea. Change-Id: I6040f2f2e0285d6872686c7db2edfc08df4522bc diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index b1ccda130..1d9cbcaba 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -185,6 +185,12 @@ L.ClipboardContainer = L.Layer.extend({ this._textArea.setAttribute('autocomplete', 'off'); this._textArea.setAttribute('spellcheck', 'false'); + // Prevent automatic line breaks in the textarea. Without this, + // chromium/blink will trigger input/insertLineBreak events by + // just adding whitespace. + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-wrap + this._textArea.setAttribute('wrap', 'off'); + this._setupStyles(); this._emptyArea(); commit 8a2e7d8c8a6c0a0981cf6d8b8e5673e168a0e6be Author: Michael Meeks <[email protected]> AuthorDate: Mon Jul 22 17:01:03 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 input: start again - switch to using textarea and diffing it's content. A much simpler, and more robust approach. Change-Id: I855818e69845212523848464a4ab07d22a762dba diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index d89c7fa9a..b1ccda130 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -2,68 +2,25 @@ /* * L.ClipboardContainer is the hidden textarea, which handles text * input events and clipboard selection. + * */ /* global */ L.ClipboardContainer = L.Layer.extend({ initialize: function() { - // Queued input - this shall be sent to lowsd after a short timeout, - // and might be canceled in the event of a 'deleteContentBackward' - // input event, to account for predictive keyboard behaviour. - this._queuedInput = ''; - this._queueTimer = undefined; - - // Flag to denote the composing state, derived from compositionstart/compositionend events. - // Needed for a edge case in Chrome+AOSP where an - // "input/deleteContentBackward" event is fired with "isComposing" set - // to false even though it happens *before* a "compositionend" event. - // Also for some cases in desktop Safari when an InputEvent doesn't have a "isComposing" - // property (and therefore evaluates to "undefined") + // Flag to denote the composing state, derived from + // compositionstart/compositionend events; unused this._isComposing = false; - // Stores the range(s) of the last 'beforeinput' event, so that the input event - // can access it. - this._lastRanges = []; - - // Stores the data of the last 'compositionstart' event. Needed to abort - // composition when going back to spellcheck a word in FFX/Gecko + GBoard. - // this._lastCompositionStartData = []; - - // Stores the type of the last 'input' event. Needed to abort composition - // when going back to spellcheck a word in FFX/Gecko + AnySoftKeyboard and - // some other scenarios. - this._lastInputType = ''; - - // Length of the document's selection when the last 'beforeinput' event was - // handled. Needed to catch and handle an edge case in Chrome where hitting - // either delete or backspace with active selection sends messages for both - // the input event and the keystrokes. - this._selectionLengthAtBeforeInput = 0; - - // Idem to _lastInputType. Needed to handle the right keystroke on the edge case - // that this._selectionLengthAtBeforeInput helps catch. - this._lastBeforeInputType = ''; - - // Capability check. - this._hasInputType = window.InputEvent && 'inputType' in window.InputEvent.prototype; - - // The "normal" order of composition events is: - // - compositionstart - // - compositionupdate - // - input/insertCompositionText - // But if the user goes back to a previous word for spellchecking, the browser - // might fire a compositionupdate *without* a corresponding input event later. - // In that case, the composition has to be aborted. Because of the order of - // the events, a timer is needed to check for the right conditions. - this._abortCompositionTimeout = undefined; - - // Defines whether to use a <input type=textarea> (when true) or a - // <div contenteditable> (when false) - this._legacyArea = L.Browser.safari; + // Clearing the area can generate input events + this._ignoreInputCount = 0; + + // Content + this._lastContent = []; // unicode characters // Debug flag, used in fancyLog(). See the debug() method. -// this._isDebugOn = true; +// this._isDebugOn = true; this._isDebugOn = false; this._initLayout(); @@ -77,13 +34,8 @@ L.ClipboardContainer = L.Layer.extend({ draggable: true }).on('dragend', this._onCursorHandlerDragEnd, this); - // Used for internal cut/copy/paste in the same document - to tell - // lowsd whether to use its internal clipboard state (rich text) or to send - // the browser contents (plaintext) - this._lastClipboardText = undefined; - - // This variable prevents from hiding the keyboard just before focus call - this.dontBlur = false; + var that = this; + this._selectionHandler = function(ev) { that._onEvent(ev); } }, onAdd: function() { @@ -104,9 +56,6 @@ L.ClipboardContainer = L.Layer.extend({ } L.DomEvent.on(this._map.getContainer(), 'mousedown touchstart', this._abortComposition, this); - // L.DomEvent.on(this._map.getContainer(), 'mousedown touchstart', function(ev) { - // this._fancyLog(ev.type); - // }, this); }, onRemove: function() { @@ -121,62 +70,29 @@ L.ClipboardContainer = L.Layer.extend({ }, _onFocusBlur: function(ev) { -// console.log(ev.type, performance.now(), ev); - - if (this.dontBlur && ev.type == 'blur') { - this._map.focus(); - this.dontBlur = false; - return; - } - - if (this.dontBlur && ev.type == 'blur') { - this._map.focus(); - this.dontBlur = false; - return; - } + this._fancyLog(ev.type, ''); var onoff = (ev.type == 'focus' ? L.DomEvent.on : L.DomEvent.off).bind(L.DomEvent); - onoff(this._textArea, 'compositionstart', this._onCompositionStart, this); - onoff(this._textArea, 'compositionend', this._onCompositionEnd, this); - onoff(this._textArea, 'beforeinput', this._onBeforeInput, this); - onoff(this._textArea, 'cut copy', this._onCutCopy, this); - onoff(this._textArea, 'paste', this._onPaste, this); - onoff(this._textArea, 'input', this._onInput, this); - onoff(this._textArea, 'keyup', this._onKeyUp, this); - - if (L.Browser.ie) { - onoff(this._textArea, 'textinput', this._onMSIETextInput, this); - onoff(this._textArea, 'keydown', this._onMSIEKeyDown, this); - } - if (L.Browser.edge) { - onoff(this._textArea, 'keydown', this._onEdgeKeyDown, this); - } - - // Stock android browsers (using an embedded WebView) wihout an InputEvent - // implementation behave similar to MSIE in regards to "enter" & "delete" - // keypresses - if (L.Browser.android && L.Browser.mobileWebkit3d && !('InputEvent' in window)) { - onoff(this._textArea, 'keydown', this._onMSIEKeyDown, this); - } - - // Debug + // Debug - connect first for saner logging. onoff( this._textArea, - 'copy cut compositionstart compositionupdate compositionend select selectionstart selectionchange keydown keypress keyup beforeinput textInput textinput input', + 'copy cut compositionstart compositionupdate compositionend select keydown keypress keyup beforeinput textInput textinput input', this._onEvent, this ); + onoff(this._textArea, 'input', this._onInput, this); + onoff(this._textArea, 'compositionstart', this._onCompositionStart, this); + onoff(this._textArea, 'compositionupdate', this._onCompositionUpdate, this); + onoff(this._textArea, 'compositionend', this._onCompositionEnd, this); + onoff(this._textArea, 'keyup', this._onKeyUp, this); + onoff(this._textArea, 'copy cut paste', this._map._handleDOMEvent, this._map); + this._map.notifyActive(); - if (ev.type === 'blur') { - if (this._isComposing) { - this._queueInput(this._compositionText); - } - this._abortComposition(); - } else { - this._winId = 0; + if (ev.type === 'blur' && this._isComposing) { + this._abortComposition(ev); } }, @@ -196,16 +112,7 @@ L.ClipboardContainer = L.Layer.extend({ // Marks the content of the textarea/contenteditable as selected, // for system clipboard interaction. select: function select() { - if (this._legacyArea) { - this._textArea.select(); - } else { - // As per https://stackoverflow.com/a/6150060/4768502 - var range = document.createRange(); - range.selectNodeContents(this._textArea); - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - } + this._textArea.select(); }, warnCopyPaste: function() { @@ -219,11 +126,31 @@ L.ClipboardContainer = L.Layer.extend({ }, getValue: function() { - if (this._legacyArea) { - return this._textArea.value; - } else { - return this._textArea.textContent; + var value = this._textArea.value; + // kill unwanted entities + value = value.replace(/ /g, ' '); + return value; + }, + + getValueAsCodePoints: function() { + var value = this.getValue(); + var arr = []; + var code; + for (var i = 0; i < value.length; ++i) + { + code = value.charCodeAt(i); + + // if it were not for IE11: "for (code of value)" does the job. + if (code >= 0xd800 && code <= 0xdbff) // handle UTF16 pairs. + { + // TESTME: harder ... + var high = (code - 0xd800) << 10; + code = value.charCodeAt(++i); + code = high + code - 0xdc00 + 0x100000; + } + arr.push(code); } + return arr; }, setValue: function(val) { @@ -251,18 +178,7 @@ L.ClipboardContainer = L.Layer.extend({ // The textarea allows the keyboard to pop up and so on. // Note that the contents of the textarea are NOT deleted on each composed // word, in order to make - - if (this._legacyArea) { - // Force a textarea on Safari. This is two-fold: Safari doesn't fire - // input/insertParagraph events on an empty&focused contenteditable, - // but does fire input/insertLineBreak on an empty&focused textarea; - // Safari on iPad would show bold/italic/underline native controls - // which cannot be handled with the current implementation. - this._textArea = L.DomUtil.create('textarea', 'clipboard', this._container); - } else { - this._textArea = L.DomUtil.create('div', 'clipboard', this._container); - this._textArea.setAttribute('contenteditable', 'true'); - } + this._textArea = L.DomUtil.create('textarea', 'clipboard', this._container); this._textArea.setAttribute('autocapitalize', 'off'); this._textArea.setAttribute('autofocus', 'true'); this._textArea.setAttribute('autocorrect', 'off'); @@ -270,6 +186,8 @@ L.ClipboardContainer = L.Layer.extend({ this._textArea.setAttribute('spellcheck', 'false'); this._setupStyles(); + + this._emptyArea(); }, _setupStyles: function() { @@ -346,42 +264,21 @@ L.ClipboardContainer = L.Layer.extend({ L.DomUtil.setPosition(this._container, pos); }, - // Return the content of _lastRanges as a string. - _lastRangesString: function() { - if ( - this._lastRanges[0] && - 'startOffset' in this._lastRanges[0] && - 'endOffset' in this._lastRanges[0] - ) { - return this._lastRanges[0].startOffset + '-' + this._lastRanges[0].endOffset; - } - - return undefined; - }, - // Generic handle attached to most text area events, just for debugging purposes. _onEvent: function _onEvent(ev) { var msg = { - type: ev.type, inputType: ev.inputType, data: ev.data, key: ev.key, isComposing: ev.isComposing }; - msg.lastRanges = this._lastRangesString(); - - if (ev.type === 'input') { - msg.inputType = ev.inputType; - } - if ('key' in ev) { msg.key = ev.key; msg.keyCode = ev.keyCode; msg.code = ev.code; msg.which = ev.which; } - this._fancyLog(ev.type, msg); }, @@ -397,8 +294,37 @@ L.ClipboardContainer = L.Layer.extend({ // Pretty-print on console (but only if "tile layer debug mode" is active) if (this._isDebugOn) { + var state = this._isComposing ? 'C' : 'N'; + state += ' '; + + var sel = window.getSelection(); + var content = this.getValue(); + if (sel === null) + state += '-1'; + else + { + state += sel.rangeCount; + + state += ' '; + var cursorPos = -1; + for (var i = 0; i < sel.rangeCount; ++i) + { + var range = sel.getRangeAt(i); + state += range.startOffset + '-' + range.endOffset + ' '; + if (cursorPos < 0) + cursorPos = range.startOffset; + } + if (sel.toString() !== '') + state += ': "' + sel.toString() + '" '; + + // inject probable cursor + if (cursorPos >= 0) + content = content.slice(0, cursorPos) + '|' + content.slice(cursorPos); + } + console.log2( - +new Date() + ' %cINPUT%c: ' + type + '%c', + + new Date() + ' %cINPUT%c: ' + state + + '"' + content + '" ' + type + '%c ', 'background:#bfb;color:black', 'color:green', 'color:black', @@ -408,153 +334,55 @@ L.ClipboardContainer = L.Layer.extend({ }, // Fired when text has been inputed, *during* and after composing/spellchecking - _onInput: function _onInput(ev) { + _onInput: function _onInput(/* ev */) { this._map.notifyActive(); - var previousInputType = this._lastInputType; - this._lastInputType = ev.inputType; - - if (!('inputType' in ev)) { - // Legacy MSIE or Android WebView, just send the contents of the - // container and clear it. - if (this._isComposing) { - this._sendCompositionEvent('input', this._textArea.textContent); - } else { - if ( - this._textArea.textContent.length === 0 && - this._textArea.innerHTML.indexOf('<br>') !== -1 - ) { - // WebView-specific hack: when the user presses enter, textContent - // is empty instead of "\n", but a <br> is added to the - // contenteditable. - this._sendText('\n'); - } else { - this._sendText(this._textArea.textContent); - } - this._emptyArea(); - } - } else if (ev.inputType === 'insertCompositionText') { - // The text being composed has changed. - // This is diferent from a 'compositionupdate' event: a 'compositionupdate' - // event might be fired when going back to spellcheck a word, but an - // 'input/insertCompositionText' happens only when the user is adding to a - // composition. - - // Abort composition when going back for spellchecking, FFX/Gecko - if (L.Browser.gecko && previousInputType === 'deleteContentBackward') { - return; - } - - clearTimeout(this._abortCompositionTimeout); - - if (!this._isComposing) { - // FFX/Gecko: Regardless of on-screen keyboard, there is a - // input/insertCompositionText with isComposing=false *after* - // the compositionend event. - this._queueInput(ev.data); - } else { - // Flush the queue - if (this._queuedInput !== '') { - this._sendQueued(); - } + if (this._ignoreInputCount > 0) { + console.log('ignoring synthetic input ' + this._ignoreInputCount); + return; + } - // Tell lowsd about the current text being composed - this._sendCompositionEvent('input', ev.data); - } - } else if (ev.inputType === 'insertText') { - // Non-composed text has been added to the text area. - - // FFX+AOSP / FFX+AnySoftKeyboard edge case: Autocompleting a - // one-letter word will fire a input/insertText with that word - // right after a compositionend + input/insertCompositionText. - // In that case, ignore the - if ( - L.Browser.gecko && - ev.data.length === 1 && - previousInputType === 'insertCompositionText' && - ev.data === this._queuedInput - ) { - return; - } + var content = this.getValueAsCodePoints(); - if (!this._isComposing) { - this._queueInput(ev.data); - } - } else if (ev.inputType === 'insertParagraph') { - // Happens on non-Safari on the contenteditable div. - this._queueInput('\n'); + // We use a different leading and terminal space character + // to differentiate backspace from delete, then replace the character. + if (content[0] !== 16*10) { // missing initial non-breaking space. + console.log('Sending backspace'); + this._removeTextContent(1, 0); this._emptyArea(); - } else if (ev.inputType === 'insertLineBreak') { - // Happens on Safari on the textarea. - this._queueInput('\n'); + return; + } + if (content[content.length-1] !== 32) { // missing trailing space. + console.log('Sending delete'); + this._removeTextContent(0, 1); this._emptyArea(); - } else if (ev.inputType === 'deleteContentBackward') { - if (this._isComposing) { - // deletion refers to the text being composed, noop - return; - } + return; + } - // Delete text backwards - as many characters as indicated in the previous - // 'beforeinput' event + // remove leading & tailing spaces. + content = content.slice(1, -1); - // These are sent e.g. by the GBoard keyboard when autocorrecting, meaning - // "I'm about to send another textInput event with the right word". + var matchTo = 0; + var sharedLength = Math.min(content.length, this._lastContent.length); + while (matchTo < sharedLength && content[matchTo] === this._lastContent[matchTo]) + matchTo++; - var count = 1; - if (this._lastRanges[0]) { - count = this._lastRanges[0].endOffset - this._lastRanges[0].startOffset; - } + console.log('Comparison matchAt ' + matchTo + '\n' + + '\tnew "' + String.fromCharCode.apply(null, content) + '" (' + content.length + ')' + '\n' + + '\told "' + String.fromCharCode.apply(null, this._lastContent) + '" (' + this._lastContent.length + ')'); - // If there is queued input, cancel that first. This prevents race conditions - // in lowsd (compose-backspace-compose messages are handled as - // compose-compose-backspace). - // Deleting queued input happens when accepting an autocorrect suggestion; - // emptying the area in that case would break text composition workflow. - var l = this._queuedInput.length; - if (l >= count) { - this._queuedInput = this._queuedInput.substring(0, l - count); - } else { - this._removeTextContext(count, 0); - this._emptyArea(); - } + var remove = this._lastContent.length - matchTo; + if (remove > 0) + this._removeTextContent(remove, 0); - L.DomEvent.stop(ev); - } else if (ev.inputType === 'deleteContentForward') { - // Send a UNO 'delete' keystroke - this._sendKeyEvent(46, 1286); - this._emptyArea(); - } else if (ev.inputType === 'insertReplacementText') { - // Happens only in Safari (both iOS and OS X) with autocorrect/spellcheck - // FIXME: It doesn't provide any info about how much to replace! - // This is currently disabled by means of using a <input type=textarea - // autocorrect=off> in Safari. - /// TODO: Send a specific message to lowsd to find the last word and - /// replace it with the given one. - } else if (ev.inputType === 'deleteCompositionText') { - // Safari on OS X is extra nice about composition - it notifies the - // browser whenever the composition text should be deleted. - } else if (ev.inputType === 'insertFromComposition') { - // Observed only on desktop Safari just before a "compositionend" - // TODO: Check if the - this._queueInput(ev.data); - } else if (ev.inputType === 'deleteByCut') { - // Called when Ctrl+X'ing - this._abortComposition(ev); - } else { - console.error('Unhandled type of input event!!', ev.inputType, ev); - throw new Error('Unhandled type of input event!'); - } - }, + var newText = content; + if (matchTo > 0) + newText = newText.slice(matchTo); - // Chrome and MSIE (from 9 all the way up to Edge) send the non-standard - // "textInput" DOM event. - // In Chrome, this is fired *just before* the compositionend event, and *before* - // any other "input" events which would add text to the area (e.g. "insertText") - // "textInput" events are used in MSIE, since the "input" events do not hold - // information about the text added to the area. - // In MSIE11, the event is "textinput" (all lowercase). - _onMSIETextInput: function _onInput(ev) { - this._queueInput(ev.data); + this._lastContent = content; + + if (newText.length > 0) + this._sendText(String.fromCharCode.apply(null, newText)); }, // Sends the given (UTF-8) string of text to lowsd, as IME (text composition) @@ -565,10 +393,14 @@ L.ClipboardContainer = L.Layer.extend({ // MSIE/Edge cannot compare a string to "\n" for whatever reason, // so compare charcode as well if (text === '\n' || (text.length === 1 && text.charCodeAt(0) === 13)) { - // The composition messages doesn't play well with just a line break, - // therefore send a keystroke. - this._sendKeyEvent(13, 1280); - this._emptyArea(); + // we get a duplicate key-event on Gecko, oddly so drop it. + if (!L.Browser.gecko) + { + // The composition messages doesn't play well with just a line break, + // therefore send a keystroke. + this._sendKeyEvent(13, 1280); + this._emptyArea(); + } } else { // The composition messages doesn't play well with line breaks inside // the composed word (e.g. word and a newline are queued client-side @@ -596,161 +428,34 @@ L.ClipboardContainer = L.Layer.extend({ // (some combination of browser + input method don't fire those on an // empty contenteditable). _emptyArea: function _emptyArea() { - if (this._hasInputType) { - // Note: 0xA0 is 160, which is the character code for non-breaking space: - // https://www.fileformat.info/info/unicode/char/00a0/index.htm - // Using normal spaces would make FFX/Gecko collapse them into an - // empty string. - if (this._legacyArea) { - this._textArea.value = '\xa0\xa0'; - /// TODO: Check that this selection method works with MSIE11 - /// - this._textArea.setSelectionRange(1, 1); - } else { - // The strategy for a contenteditable is to add a space, select it, - // collapse the selection, then add two text nodes after and before - // the text node with the selection but with a delay of one frame. - // The frame delay is done in order to avoid the AOSP on-screen keyboard - // from moving the cursor caret around. On delete/backspace, AOSP - // keyboard would somehow ignore the selection ranges and move the caret - // before/after the empty spaces. - - /// FIXME: The aforementioned strategy makes Android + GBoard + Firefox fail: - /// trying to press the "ArrowLeft" or "ArrowUp" keys in the - this._textArea.innerText = '\xa0'; - - var range = document.createRange(); - range.selectNodeContents(this._textArea); - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - sel.collapse(this._textArea.childNodes[0]); - - L.Util.requestAnimFrame(function() { - this._textArea.prepend('\xa0'); - this._textArea.append('\xa0'); - }.bind(this)); - - } - } else if (this._legacyArea) { - this._textArea.value = ''; - } else { - // In order to empty a contenteditable when the two-spaces-hack is not - // in place, access its first text node child and empty it. - if (this._textArea.childNodes.length === 1) { - this._textArea.childNodes[0].data = ''; - } else if (this._textArea.childNodes.length > 1) { - this._textArea.innerText = ''; - this._textArea.innerHTML = ''; - // Sanity check, should never be reached. - // True - but for now - lets not kill the world in this case ... -// throw new Error('Unexpected: more than one text node inside the contenteditable.'); - } - } - }, - - // The getTargetRanges() method usually returns an empty array, - // since the ranges are only valid at the "beforeinput" stage. - // Fetching this info for later is important, especially - // for Chrome+"input/deleteContentBackward" events. - // Also, some deleteContentBackward/Forward input types - // only happen at 'beforeinput' and not at 'input' events, - // particularly when the textarea/contenteditable is empty, but - // only in some configurations. - _onBeforeInput: function _onBeforeInput(ev) { - this._lastRanges = ev.getTargetRanges(); - // console.log('onBeforeInput range: ', ev.inputType, ranges, - // ranges[0] && ranges[0].startOffset, - // ranges[0] && ranges[0].endOffset); - - this._lastBeforeInputType = ev.inputType; - this._selectionLengthAtBeforeInput = selection.length; - - this._fancyLog('beforeinput selection', window.getSelection().toString()); - this._fancyLog('beforeinput range', this._lastRangesString()); - - // FIXME: a hack - this assumes that nothing changed / no auto-correction inside the Kit. - // FIXME: only mobile for now to reduce risk ... - if (window.mode.isMobile() || window.mode.isTablet()) - { - var seltext = window.getSelection(); - if (!this._isComposing && seltext && seltext.toString() && seltext.toString().length > 0) - { - var len = seltext.toString().length; - this._fancyLog('selection overtype', seltext.toString() + ' len ' + len + ' chars "' + this._queuedInput + '"'); - if (this._queuedInput) - { - var size = this._queuedInput.length; - var redux = Math.min(size, len); - this._queuedInput = this._queuedInput.slice(0,-redux); - len -= redux; - console.log2('queue overtype', 'removed ' + redux + ' from queued input to "' + this._queuedInput + '"'); - } - // FIXME: this is the more hacky bit - if the Kit changed under us. - for (var i = 0; i < len; ++i) - this._sendKeyEvent(8, 1283); - } - } + this._fancyLog('empty-area'); - // When trying to delete (i.e. backspace) on an empty textarea, the input event - // won't be fired afterwards. Handle backspace here instead. - - // Chrome + AOSP does *not* send any "beforeinput" events when the - // textarea is empty. In that case, a 'keydown'+'keypress'+'keyup' sequence - // for charCode=8 is fired, and handled by the Map.Keyboard.js. - if ((this._winId === 0 && this._textArea.textContent.length === 0) || - ev.findMyTextContentAre.length == 0) { - if (ev.inputType === 'deleteContentBackward') { - this._sendKeyEvent(8, 1283); - } else if (ev.inputType === 'deleteContentForward') { - this._sendKeyEvent(46, 1286); - } - } - }, + this._ignoreInputCount++; + // Note: 0xA0 is 160, which is the character code for non-breaking space: + // https://www.fileformat.info/info/unicode/char/00a0/index.htm + // Using normal spaces would make FFX/Gecko collapse them into an + // empty string. - _queueInput: function _queueInput(text) { - this._map.notifyActive(); + console.log('Set old/lastContent to empty'); + this._lastContent = []; - if (text === null) { - // Chrome sends a input/insertText with 'null' event data when - // typing a newline quickly after typing text. - console.warn('Tried to queue null text! Maybe a lost newline?'); - this._queuedInput += '\n'; - clearTimeout(this._queueTimer); - } - else if (this._queuedInput !== '') { - console.warn( - 'Text input already queued - recieving composition end events too fast!' - ); - this._queuedInput += text; - clearTimeout(this._queueTimer); - } else { - this._queuedInput = text; - } + this._textArea.value = '\xa0 '; + /// TODO: Check that this selection method works with MSIE11 + this._textArea.setSelectionRange(1, 1); - //console.log('_queueInput', text, ' queue is now:', {text: this._queuedInput}); - this._queueTimer = setTimeout(this._sendQueued.bind(this), 50); - }, - - _clearQueued: function _clearQueued() { - // console.log('Cleared queued:', { text: this._queuedInput }); - clearTimeout(this._queueTimer); - this._queuedInput = ''; - }, - - _sendQueued: function _sendQueued() { - // console.log('Sending to lowsd (queued): ', {text: this._queuedInput}); - this._sendText(this._queuedInput); - this._clearQueued(); + this._ignoreInputCount--; }, _onCompositionStart: function _onCompositionStart(/*ev*/) { this._isComposing = true; }, - // _onCompositionUpdate: function _onCompositionUpdate(ev) { - // // Noop - handled at input/insertCompositionText instead. - // }, + // Handled only in legacy situations ('input' events with an inputType + // property are preferred). + _onCompositionUpdate: function _onCompositionUpdate(ev) { + this._map.notifyActive(); + this._onInput(ev); + }, // Chrome doesn't fire any "input/insertCompositionText" with "isComposing" set to false. // Instead , it fires non-standard "textInput" events, but those can be tricky @@ -758,37 +463,9 @@ L.ClipboardContainer = L.Layer.extend({ // The approach here is to use "compositionend" events *only in Chrome* to mark // the composing text as committed to the text area. _onCompositionEnd: function _onCompositionEnd(ev) { - // Check for standard chrome, and check heuristically for embedded Android - // WebView (without chrome user-agent string) - if (L.Browser.chrome || (L.Browser.android && L.Browser.webkit3d && !L.Browser.webkit)) { - if (this._lastInputType === 'insertCompositionText') { -// console.log('Queuing input because android webview'); - this._queueInput(ev.data); - } else { - // Ended a composition without user input, abort. - // This happens on Chrome+GBoard when autocompleting a word - // then entering a punctuation mark. - this._abortComposition(ev); - } - } - - // Check for Safari; it fires composition events on typing diacritics with dead keys. - if (L.Browser.Safari) { - if (this._lastInputType === 'insertFromComposition') { - this._queueInput(ev.data); - } else { - this._abortComposition(ev); - } - } - - // Tell lowsd to exit composition mode when the composition is empty - // This happens when deleting the whole word being composed, e.g. - // swipe a word then press backspace. - if (ev.data === '') { - this._sendCompositionEvent('input', ''); - } - + this._map.notifyActive(); this._isComposing = false; + this._onInput(ev); }, // Called when the user goes back to a word to spellcheck or replace it, @@ -797,11 +474,8 @@ L.ClipboardContainer = L.Layer.extend({ // empty the text area. _abortComposition: function _abortComposition(ev) { this._fancyLog('abort-composition', ev.type); - if (this._isComposing) { - this._sendCompositionEvent('input', ''); - this._sendCompositionEvent('end', ''); + if (this._isComposing) this._isComposing = false; - } this._emptyArea(); }, @@ -883,33 +557,14 @@ L.ClipboardContainer = L.Layer.extend({ } }, - // MSIE11 doesn't send any "textinput" events on enter, delete or backspace. - // (Idem for old-ish stock android browsers which do not implement InputEvents) - // To handle those, an event handler is added to the "keydown" event (which repeats) - _onMSIEKeyDown: function _onMSIEKeyDown(ev) { - if (!ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey) { - if (ev.key === 'Delete' || ev.key === 'Del') { - this._sendKeyEvent(46, 1286); - this._emptyArea(); - } else if (ev.key === 'Backspace') { - this._sendKeyEvent(8, 1283); - this._emptyArea(); - } else if (ev.key === 'Enter') { - this._queueInput('\n'); - this._emptyArea(); - } - } - }, - - // Edge18 doesn't send any "input" events on delete or backspace - // To handle those, an event handler is added to the "keydown" event (which repeats) - _onEdgeKeyDown: function _onEdgeKeyDown(ev) { - // FIXME: we need enter too - share with above method ? - this._onMSIEKeyDown(ev); - }, + // Used in the deleteContentBackward for deleting multiple characters with a single + // message. + // Will remove characters from the queue first, if there are any. + _removeTextContent: function _removeTextContent(before, after) { + console.log('Remove ' + before + ' before, and ' + after + ' after'); - // Used in the deleteContentBackward for deleting multiple characters with a single message. - _removeTextContext: function _removeTextContext(before, after) { + /// TODO: rename the event to 'removetextcontent' as soon as lowsd supports it + /// TODO: Ask Marco about it this._map._socket.sendMessage( 'removetextcontext id=' + this._map.getWinId() + @@ -921,6 +576,7 @@ L.ClipboardContainer = L.Layer.extend({ // Tiny helper - encapsulates sending a 'textinput' websocket message. // "type" is either "input" for updates or "end" for commits. _sendCompositionEvent: function _sendCompositionEvent(type, text) { + console.log('sending to lowsd: ', type, text); this._map._socket.sendMessage( 'textinput id=' + this._map.getWinId() + @@ -932,16 +588,22 @@ L.ClipboardContainer = L.Layer.extend({ }, // Tiny helper - encapsulates sending a 'key' or 'windowkey' websocket message - _sendKeyEvent: function _sendKeyEvent(charCode, unoKeyCode) { + // "type" can be "input" (default) or "up" + _sendKeyEvent: function _sendKeyEvent(charCode, unoKeyCode, type) { + if (!type) { + type = 'input'; + } if (this._map.getWinId() === 0) { this._map._socket.sendMessage( - 'key type=input char=' + charCode + ' key=' + unoKeyCode + '\n' + 'key type=' + type + ' char=' + charCode + ' key=' + unoKeyCode + '\n' ); } else { this._map._socket.sendMessage( 'windowkey id=' + this._map.getWinId() + - ' type=input char=' + + ' type=' + + type + + ' char=' + charCode + ' key=' + unoKeyCode + commit 586e8159a131f2860b7b632cc62994d1d989169e Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Thu Jul 4 15:53:23 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: Unconditionally apply autocapitalize=false to ClipboardContainer Change-Id: Ib875caac26920a3ed18a7ca53fbba587cebc89b5 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 68e27d4d8..d89c7fa9a 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -259,16 +259,15 @@ L.ClipboardContainer = L.Layer.extend({ // Safari on iPad would show bold/italic/underline native controls // which cannot be handled with the current implementation. this._textArea = L.DomUtil.create('textarea', 'clipboard', this._container); - this._textArea.setAttribute('autocorrect', 'off'); - this._textArea.setAttribute('autocapitalize', 'off'); - this._textArea.setAttribute('autocomplete', 'off'); - this._textArea.setAttribute('spellcheck', 'false'); - this._textArea.setAttribute('autofocus', 'true'); } else { this._textArea = L.DomUtil.create('div', 'clipboard', this._container); this._textArea.setAttribute('contenteditable', 'true'); - this._textArea.setAttribute('autofocus', 'true'); } + this._textArea.setAttribute('autocapitalize', 'off'); + this._textArea.setAttribute('autofocus', 'true'); + this._textArea.setAttribute('autocorrect', 'off'); + this._textArea.setAttribute('autocomplete', 'off'); + this._textArea.setAttribute('spellcheck', 'false'); this._setupStyles(); }, commit 8741339064da810cfaa7af6a98b06a0da7de2932 Author: Marco Cecchetti <[email protected]> AuthorDate: Wed Jul 10 10:29:50 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 single msg for deleting multiple characters Change-Id: I589dbc933e4450d5dbcf35e99b1a55598d3dee76 diff --git a/bundled/include/LibreOfficeKit/LibreOfficeKit.h b/bundled/include/LibreOfficeKit/LibreOfficeKit.h index 93b430f5d..6060b015b 100644 --- a/bundled/include/LibreOfficeKit/LibreOfficeKit.h +++ b/bundled/include/LibreOfficeKit/LibreOfficeKit.h @@ -408,6 +408,12 @@ struct _LibreOfficeKitDocumentClass /// @see lok::Document::getSelectionType int (*getSelectionType) (LibreOfficeKitDocument* pThis); + /// @see lok::Document::removeTextContext + void (*removeTextContext) (LibreOfficeKitDocument* pThis, + unsigned nWindowId, + int nBefore, + int nAfter); + #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY }; diff --git a/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx b/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx index 517a38a50..a695c4113 100644 --- a/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx +++ b/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx @@ -728,6 +728,19 @@ public: return mpDoc->pClass->resizeWindow(mpDoc, nWindowId, width, height); } + /** + * For deleting many characters all at once + * + * @param nWindowId Specify the window id to post the input event to. If + * nWindow is 0, the event is posted into the document + * @param nBefore The characters to be deleted before the cursor position + * @param nAfter The characters to be deleted after the cursor position + */ + void removeTextContext(unsigned nWindowId, int nBefore, int nAfter) + { + mpDoc->pClass->removeTextContext(mpDoc, nWindowId, nBefore, nAfter); + } + #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY }; diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp index 2880e73d0..7425e4fac 100644 --- a/kit/ChildSession.cpp +++ b/kit/ChildSession.cpp @@ -294,7 +294,8 @@ bool ChildSession::_handleInput(const char *buffer, int length) tokens[0] == "signdocument" || tokens[0] == "uploadsigneddocument" || tokens[0] == "exportsignanduploaddocument" || - tokens[0] == "rendershapeselection"); + tokens[0] == "rendershapeselection" || + tokens[0] == "removetextcontext"); if (tokens[0] == "clientzoom") { @@ -414,6 +415,10 @@ bool ChildSession::_handleInput(const char *buffer, int length) { return renderShapeSelection(buffer, length, tokens); } + else if (tokens[0] == "removetextcontext") + { + return removeTextContext(buffer, length, tokens); + } else { assert(false && "Unknown command token."); @@ -2094,6 +2099,27 @@ bool ChildSession::renderShapeSelection(const char* /*buffer*/, int /*length*/, return true; } +bool ChildSession::removeTextContext(const char* /*buffer*/, int /*length*/, + const std::vector<std::string>& tokens) +{ + int id, before, after; + std::string text; + if (tokens.size() < 4 || + !getTokenInteger(tokens[1], "id", id) || id < 0 || + !getTokenInteger(tokens[2], "before", before) || + !getTokenInteger(tokens[3], "after", after)) + { + sendTextFrame("error: cmd=" + std::string(tokens[0]) + " kind=syntax"); + return false; + } + + std::unique_lock<std::mutex> lock(getLock()); + getLOKitDocument()->setView(_viewId); + getLOKitDocument()->removeTextContext(id, before, after); + + return true; +} + /* If the user is inactive we have to remember important events so that when * the user becomes active again, we can replay the events. */ diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp index 3e32f311d..88166a0c4 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -276,6 +276,7 @@ private: bool uploadSignedDocument(const char* buffer, int length, const std::vector<std::string>& tokens); bool exportSignAndUploadDocument(const char* buffer, int length, const std::vector<std::string>& tokens); bool renderShapeSelection(const char* buffer, int length, const std::vector<std::string>& tokens); + bool removeTextContext(const char* /*buffer*/, int /*length*/, const std::vector<std::string>& tokens); void rememberEventsForInactiveUser(const int type, const std::string& payload); diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 68068abf9..68e27d4d8 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -515,11 +515,7 @@ L.ClipboardContainer = L.Layer.extend({ if (l >= count) { this._queuedInput = this._queuedInput.substring(0, l - count); } else { - for (var i = 0; i < count; i++) { - // Send a UNO backspace keystroke per glyph to be deleted - this._sendKeyEvent(8, 1283); - } - + this._removeTextContext(count, 0); this._emptyArea(); } @@ -913,6 +909,16 @@ L.ClipboardContainer = L.Layer.extend({ this._onMSIEKeyDown(ev); }, + // Used in the deleteContentBackward for deleting multiple characters with a single message. + _removeTextContext: function _removeTextContext(before, after) { + this._map._socket.sendMessage( + 'removetextcontext id=' + + this._map.getWinId() + + ' before=' + before + + ' after=' + after + ); + }, + // Tiny helper - encapsulates sending a 'textinput' websocket message. // "type" is either "input" for updates or "end" for commits. _sendCompositionEvent: function _sendCompositionEvent(type, text) { diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 85469f6ec..f7c1d35ca 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -426,7 +426,8 @@ bool ClientSession::_handleInput(const char *buffer, int length) tokens[0] != "rendershapeselection" && tokens[0] != "removesession" && tokens[0] != "renamefile" && - tokens[0] != "resizewindow") + tokens[0] != "resizewindow" && + tokens[0] != "removetextcontext") { LOG_ERR("Session [" << getId() << "] got unknown command [" << tokens[0] << "]."); sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown"); commit 1f80d99a7e373685bc52f4218d7a94b12b4aec25 Author: Michael Meeks <[email protected]> AuthorDate: Wed Jul 3 20:58:04 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Cleanup the fix - and reduce risk by special-casing for iOS / Android. Change-Id: I3a86cfcfaa2ce0664aaeccf2c2fb5bd7e980de19 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 1bcb3a226..68068abf9 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -675,22 +675,26 @@ L.ClipboardContainer = L.Layer.extend({ this._fancyLog('beforeinput range', this._lastRangesString()); // FIXME: a hack - this assumes that nothing changed / no auto-correction inside the Kit. - var seltext = window.getSelection(); - if (!this._isComposing && seltext && seltext.toString() && seltext.toString().length) + // FIXME: only mobile for now to reduce risk ... + if (window.mode.isMobile() || window.mode.isTablet()) { - var len = seltext.toString().length; - console.log2('Horror meeks3 hack - delete ' + len + ' chars "' + this._queudInput + '"'); - if (this._queuedInput) + var seltext = window.getSelection(); + if (!this._isComposing && seltext && seltext.toString() && seltext.toString().length > 0) { - var size = this._queuedInput.length; - var redux = Math.min(size, len); - this._queuedInput = this._queuedInput.slice(0,-redux); - len -= redux; - console.log2('Horror meeks3 hack - removed ' + redux + ' from queued input to "' + this._queuedInput + '"'); + var len = seltext.toString().length; + this._fancyLog('selection overtype', seltext.toString() + ' len ' + len + ' chars "' + this._queuedInput + '"'); + if (this._queuedInput) + { + var size = this._queuedInput.length; + var redux = Math.min(size, len); + this._queuedInput = this._queuedInput.slice(0,-redux); + len -= redux; + console.log2('queue overtype', 'removed ' + redux + ' from queued input to "' + this._queuedInput + '"'); + } + // FIXME: this is the more hacky bit - if the Kit changed under us. + for (var i = 0; i < len; ++i) + this._sendKeyEvent(8, 1283); } - // FIXME: this is the more hacky bit - if the Kit changed under us. - for (var i = 0; i < len; ++i) - this._sendKeyEvent(8, 1283); } // When trying to delete (i.e. backspace) on an empty textarea, the input event commit 2ba1e6a6ae2decdec82280f8cb41d448143507c7 Author: Michael Meeks <[email protected]> AuthorDate: Wed Jul 3 17:12:00 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Delete selection hack for testing. This is pretty grim, but - apparently semi-working. Change-Id: Ie14c7b0ff9927ecd7eb81716a9b347fa2230146c diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 0c7735df1..1bcb3a226 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -674,6 +674,25 @@ L.ClipboardContainer = L.Layer.extend({ this._fancyLog('beforeinput selection', window.getSelection().toString()); this._fancyLog('beforeinput range', this._lastRangesString()); + // FIXME: a hack - this assumes that nothing changed / no auto-correction inside the Kit. + var seltext = window.getSelection(); + if (!this._isComposing && seltext && seltext.toString() && seltext.toString().length) + { + var len = seltext.toString().length; + console.log2('Horror meeks3 hack - delete ' + len + ' chars "' + this._queudInput + '"'); + if (this._queuedInput) + { + var size = this._queuedInput.length; + var redux = Math.min(size, len); + this._queuedInput = this._queuedInput.slice(0,-redux); + len -= redux; + console.log2('Horror meeks3 hack - removed ' + redux + ' from queued input to "' + this._queuedInput + '"'); + } + // FIXME: this is the more hacky bit - if the Kit changed under us. + for (var i = 0; i < len; ++i) + this._sendKeyEvent(8, 1283); + } + // When trying to delete (i.e. backspace) on an empty textarea, the input event // won't be fired afterwards. Handle backspace here instead. commit e97e3e6a7785ff135e6b9db1500dce4ba5527ea2 Author: Ashod Nakashian <[email protected]> AuthorDate: Wed Jun 19 09:36:14 2019 -0400 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 leaflet: correctly handle backspace in dialogs The workaround for handling backspace caused double-deletes in dialogs. Change-Id: I85f1e1e89b7b802c24960b6f9b7b7e1e60af90a9 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 2a575bf66..0c7735df1 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -680,9 +680,8 @@ L.ClipboardContainer = L.Layer.extend({ // Chrome + AOSP does *not* send any "beforeinput" events when the // textarea is empty. In that case, a 'keydown'+'keypress'+'keyup' sequence // for charCode=8 is fired, and handled by the Map.Keyboard.js. - // NOTE: Ideally this should never happen, as the textarea/contenteditable - // is initialized with two non-breaking spaces when "emptied". - if (!this._hasInputType || (this._lastRangesString() === '0-0')) { + if ((this._winId === 0 && this._textArea.textContent.length === 0) || + ev.findMyTextContentAre.length == 0) { if (ev.inputType === 'deleteContentBackward') { this._sendKeyEvent(8, 1283); } else if (ev.inputType === 'deleteContentForward') { commit ae22aa322fbf3b418877d987630e3b17860b7c6c Author: Szymon Kłos <[email protected]> AuthorDate: Tue Jun 18 23:49:01 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Do not blur just before focus to keep keyboard This also reverts: 80768f782d867bf03f49a028c876b647a05d8662 (timeouts for focus and blur events) Change-Id: Ib98af7bd397954987e411473f611935a7e336ce6 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index b34853b88..2a575bf66 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -76,6 +76,14 @@ L.ClipboardContainer = L.Layer.extend({ }), draggable: true }).on('dragend', this._onCursorHandlerDragEnd, this); + + // Used for internal cut/copy/paste in the same document - to tell + // lowsd whether to use its internal clipboard state (rich text) or to send + // the browser contents (plaintext) + this._lastClipboardText = undefined; + + // This variable prevents from hiding the keyboard just before focus call + this.dontBlur = false; }, onAdd: function() { @@ -121,6 +129,12 @@ L.ClipboardContainer = L.Layer.extend({ return; } + if (this.dontBlur && ev.type == 'blur') { + this._map.focus(); + this.dontBlur = false; + return; + } + var onoff = (ev.type == 'focus' ? L.DomEvent.on : L.DomEvent.off).bind(L.DomEvent); onoff(this._textArea, 'compositionstart', this._onCompositionStart, this); @@ -172,13 +186,11 @@ L.ClipboardContainer = L.Layer.extend({ console.log('EPIC HORRORS HERE'); return; } - var that = this; - setTimeout(function() { that._textArea.focus(); }, 10); + this._textArea.focus(); }, blur: function() { - var that = this; - setTimeout(function() { that._textArea.blur(); }, 10); + this._textArea.blur(); }, // Marks the content of the textarea/contenteditable as selected, commit b7be199357f2d02ec0096fa7eb1b2ba65a15aa41 Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Wed Jun 19 14:26:55 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: ClipboardContainer: Re-enable range fetching on 'beforeinput' events Change-Id: I1d7d18b36a2152e91fe8c9db21fa7a2e9f4dc935 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index f076d98b1..b34853b88 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -651,8 +651,10 @@ L.ClipboardContainer = L.Layer.extend({ // particularly when the textarea/contenteditable is empty, but // only in some configurations. _onBeforeInput: function _onBeforeInput(ev) { - var selection = window.getSelection().toString(); this._lastRanges = ev.getTargetRanges(); + // console.log('onBeforeInput range: ', ev.inputType, ranges, + // ranges[0] && ranges[0].startOffset, + // ranges[0] && ranges[0].endOffset); this._lastBeforeInputType = ev.inputType; this._selectionLengthAtBeforeInput = selection.length; commit ae350549a811a103c1a61af79d2abd05c902eff9 Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Wed Jun 19 12:53:05 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: ClipboardContainer should behave the same in Chrome/ium... ...than in webkit-like Android WebView Change-Id: I0063e1b67a5a705eb54d6bd5e977e36c8969b71d diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 0eb4400c9..f076d98b1 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -727,13 +727,9 @@ L.ClipboardContainer = L.Layer.extend({ // The approach here is to use "compositionend" events *only in Chrome* to mark // the composing text as committed to the text area. _onCompositionEnd: function _onCompositionEnd(ev) { - this._map.notifyActive(); // Check for standard chrome, and check heuristically for embedded Android // WebView (without chrome user-agent string) - if ( - L.Browser.chrome || - (L.Browser.android && L.Browser.webkit3d && !L.Browser.webkit && !L.Browser.gecko) - ) { + if (L.Browser.chrome || (L.Browser.android && L.Browser.webkit3d && !L.Browser.webkit)) { if (this._lastInputType === 'insertCompositionText') { // console.log('Queuing input because android webview'); this._queueInput(ev.data); commit 0dc71cd79980783ad4a89b4ef90ba666fa9e42b7 Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Wed Jun 19 12:01:23 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: ClipboardContainer fix innerHTML syntax Change-Id: I3aa81d690d4b4ea3627525f5f5e53ffeecfe9b81 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index bb1c072aa..0eb4400c9 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -411,7 +411,7 @@ L.ClipboardContainer = L.Layer.extend({ } else { if ( this._textArea.textContent.length === 0 && - this._textArea.textContent.innerHTML.indexOf('<br>') !== -1 + this._textArea.innerHTML.indexOf('<br>') !== -1 ) { // WebView-specific hack: when the user presses enter, textContent // is empty instead of "\n", but a <br> is added to the commit ff3a5a18474b03602ac8dc6305761031f01b352e Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Wed Jun 19 11:22:06 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: ClipboardContainer hack for pressing enter on Android WebView Change-Id: I735fb1a856fdd1de1dc77044bb169216df6ba64f diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 5c16c9ec3..bb1c072aa 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -404,12 +404,22 @@ L.ClipboardContainer = L.Layer.extend({ this._lastInputType = ev.inputType; if (!('inputType' in ev)) { - // Legacy MSIE or Android Webkit, just send the contents of the + // Legacy MSIE or Android WebView, just send the contents of the // container and clear it. if (this._isComposing) { this._sendCompositionEvent('input', this._textArea.textContent); } else { - this._sendText(this._textArea.textContent); + if ( + this._textArea.textContent.length === 0 && + this._textArea.textContent.innerHTML.indexOf('<br>') !== -1 + ) { + // WebView-specific hack: when the user presses enter, textContent + // is empty instead of "\n", but a <br> is added to the + // contenteditable. + this._sendText('\n'); + } else { + this._sendText(this._textArea.textContent); + } this._emptyArea(); } } else if (ev.inputType === 'insertCompositionText') { commit f0c5b6d1729806cd7137d2f1f4662ecf145cb906 Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Wed Jun 19 11:02:13 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: ClipboardContainer handle composition mode for legacy input events Change-Id: I3db3e790f75de90b126ea0e63771b6035f91b0b4 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 785e6b0a7..5c16c9ec3 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -406,9 +406,12 @@ L.ClipboardContainer = L.Layer.extend({ if (!('inputType' in ev)) { // Legacy MSIE or Android Webkit, just send the contents of the // container and clear it. - this._sendText(this._textArea.textContent); - this._emptyArea(); - + if (this._isComposing) { + this._sendCompositionEvent('input', this._textArea.textContent); + } else { + this._sendText(this._textArea.textContent); + this._emptyArea(); + } } else if (ev.inputType === 'insertCompositionText') { // The text being composed has changed. // This is diferent from a 'compositionupdate' event: a 'compositionupdate' commit 4a20744c0c725ae3756981bec10a709d4b7ab1e6 Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Wed Jun 19 10:28:20 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: ClipboardContainer: Assume legacy input events on Android Webkit as well Change-Id: Iaa04f808940afaedc12c48b48c722673b3b406ab diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 9f782e392..785e6b0a7 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -404,16 +404,11 @@ L.ClipboardContainer = L.Layer.extend({ this._lastInputType = ev.inputType; if (!('inputType' in ev)) { - if (this._isComposing) { - this._sendCompositionEvent('input', this._textArea.textContent); - } else { - // Legacy MSIE, Android WebView or FFX < 66, just send the contents of the - // container and clear it. - if (this._textArea.textContent.length !== 0) { - this._sendText(this._textArea.textContent); - } - this._emptyArea(); - } + // Legacy MSIE or Android Webkit, just send the contents of the + // container and clear it. + this._sendText(this._textArea.textContent); + this._emptyArea(); + } else if (ev.inputType === 'insertCompositionText') { // The text being composed has changed. // This is diferent from a 'compositionupdate' event: a 'compositionupdate' commit 1de1784942ba1cb857cde95546dddb2dbb11fa36 Author: Szymon Kłos <[email protected]> AuthorDate: Tue Jun 18 18:14:24 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Set timeout for focus in focus events This allows events to occur in order. Change-Id: I30701dcdcb5758ea2d5f69a93203641679823d8c diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index 13d846781..9f782e392 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -172,11 +172,13 @@ L.ClipboardContainer = L.Layer.extend({ console.log('EPIC HORRORS HERE'); return; } - this._textArea.focus(); + var that = this; + setTimeout(function() { that._textArea.focus(); }, 10); }, blur: function() { - this._textArea.blur(); + var that = this; + setTimeout(function() { that._textArea.blur(); }, 10); }, // Marks the content of the textarea/contenteditable as selected, commit d525b4625690a86a86f451ac51a05778ff5c2002 Author: Iván Sánchez Ortega <[email protected]> AuthorDate: Tue Jun 18 15:20:02 2019 +0200 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 loleaflet: Explicitly relay keyboard events from LokDialog to Keyboard handler Change-Id: Icaa33537313c73286937bdeeef595bcd82037f95 diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js index 18ad2f5d7..0ef5d1a66 100644 --- a/loleaflet/src/map/handler/Map.Keyboard.js +++ b/loleaflet/src/map/handler/Map.Keyboard.js @@ -181,22 +181,13 @@ L.Map.Keyboard = L.Handler.extend({ L.DomEvent.off(this._map.getContainer(), 'keydown keyup keypress', this._onKeyDown, this); }, - /* - * Returns true whenever the key event shall be ignored. - * This means shift+insert and shift+delete (or "insert or delete when holding - * shift down"). Those events are handled elsewhere to trigger "cut" and - * "paste" events, and need to be ignored in order to avoid double-handling them. - */ - _ignoreKeyEvent: function(e) { - var shift = e.originalEvent.shiftKey; - if ('key' in e.originalEvent) { - var key = e.originalEvent.key; - return (shift && (key === 'Delete' || key === 'Insert')); - } else { - // keyCode is not reliable in AZERTY/DVORAK keyboard layouts, is used - // only as a fallback for MSIE8. - var keyCode = e.originalEvent.keyCode; - return (shift && (keyCode === 45 || keyCode === 46)); + _ignoreKeyEvent: function(ev) { + var shift = ev.shiftKey ? this.keyModifier.shift : 0; + if (shift && (ev.keyCode === 45 || ev.keyCode === 46)) { + // don't handle shift+insert, shift+delete + // These are converted to 'cut', 'paste' events which are + // automatically handled by us, so avoid double-handling + return true; } }, @@ -253,6 +244,7 @@ L.Map.Keyboard = L.Handler.extend({ // _handleKeyEvent - checks if the given keyboard event shall trigger // a message to lowsd, and calls the given keyEventFn(type, charcode, keycode) // callback if so. + // Called from private _onKeyDown _handleKeyEvent: function (ev, keyEventFn) { this._map.notifyActive(); if (this._map.slideShow && this._map.slideShow.fullscreen) { commit 8b080fee2f86dbcc7d77b5eedeab468d1f8388f6 Author: Michael Meeks <[email protected]> AuthorDate: Tue Jun 18 09:53:51 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Avoid using the read-only / edit toggle as a show keyboard button. Change-Id: I1c4e88c1824a8aef4aa2bb2e1e18943d95202bb0 diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index a6631cccb..13d846781 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -156,10 +156,13 @@ L.ClipboardContainer = L.Layer.extend({ this._map.notifyActive(); - if (ev.type === 'blur' && this._isComposing) { - /// TODO: Set this._compositionText - this._queueInput(this._compositionText); - this._abortComposition(ev); + if (ev.type === 'blur') { + if (this._isComposing) { + this._queueInput(this._compositionText); + } + this._abortComposition(); + } else { + this._winId = 0; } }, commit 0c749e0f597509122913a9b4a08c6f07e8fda517 Author: Michael Meeks <[email protected]> AuthorDate: Tue Jun 18 09:27:46 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 Default focus dialogs. Change-Id: Idc37cdfbd84bbdcea90c96175091aff6d3026da6 diff --git a/loleaflet/src/control/Control.LokDialog.js b/loleaflet/src/control/Control.LokDialog.js index b0404a593..b7a842ef4 100644 --- a/loleaflet/src/control/Control.LokDialog.js +++ b/loleaflet/src/control/Control.LokDialog.js @@ -399,8 +399,7 @@ L.Control.LokDialog = L.Control.extend({ focus: function(dlgId, force) { if (!force && (!this._isOpen(dlgId) || !this._dialogs[dlgId].cursorVisible)) return; - this._map.setWinId(dlgId); - this._map.getClipboardContainer().focus(); + this._dialogs[dlgId].input.focus(); }, _setCanvasWidthHeight: function(canvas, width, height) { commit 7e38e8fc3cb00a26a02a45105bf6e534b381746c Author: Michael Meeks <[email protected]> AuthorDate: Tue Jul 2 22:41:57 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 input: avoid exceptions during logging. Change-Id: I4dfc44688f7320c9294018904732e4343229de9b diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js index c3f070932..a6631cccb 100644 --- a/loleaflet/src/layer/marker/ClipboardContainer.js +++ b/loleaflet/src/layer/marker/ClipboardContainer.js @@ -370,6 +370,12 @@ L.ClipboardContainer = L.Layer.extend({ }, _fancyLog: function _fancyLog(type, payload) { + // Avoid unhelpful exceptions + if (payload === undefined) + payload = 'undefined'; + else if (payload === null) + payload = 'null'; + // Save to downloadable log L.Log.log(payload.toString(), 'INPUT'); commit 0c218028eb243919a66e1b3bcc42c47aa5ecde94 Author: Michael Meeks <[email protected]> AuthorDate: Tue Jul 2 22:20:31 2019 +0100 Commit: Michael Meeks <[email protected]> CommitDate: Thu Oct 3 14:50:17 2019 +0100 ... etc. - the rest is truncated _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
