[FIX] web_editor: remove virtual corrector/completion keyboard glitch
For the ours need, we can not let the contentEditable behavior. Indeed, this one does not have the same behavior according to the browser. The DOM node could be erase, merge, cut, in them according to the keyboard interactions. The problem is that if we have to manage these behaviors, we have to manage everything. It was already the case in the previous version, however before we created an editor by zone and by a hack we mix the history. In the case of the virtual keyboard of some OS / Phone, the events are different and the autocompletion does not update (bug that prevents modification interactions with javascript). In order to solve these problems, we use another event for the insertion of the characters. However, we must also force the update of the corrector by forcing a blur and a focus. The current version is a temporary version. We are currently working on a version that uses a virtual dom and does not need to do excessive prevent default.
This commit is contained in:
@@ -177,6 +177,8 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getWysiwygOptions: function () {
|
||||
var iPhone = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
|
||||
return {
|
||||
recordInfo: {
|
||||
context: this.record.getContext(this.recordParams),
|
||||
@@ -195,6 +197,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
|
||||
'SHIFT+TAB': null,
|
||||
},
|
||||
mac: {
|
||||
'ENTER': iPhone ? null : 'insertParagraph',
|
||||
'TAB': null,
|
||||
'SHIFT+TAB': null,
|
||||
},
|
||||
|
||||
@@ -9,18 +9,27 @@ dom.isAnchor = function (node) {
|
||||
return (node.tagName === 'A' || node.tagName === 'BUTTON' || $(node).hasClass('btn')) &&
|
||||
!$(node).hasClass('fa') && !$(node).hasClass('o_image');
|
||||
};
|
||||
var keycode = ({
|
||||
'8': 'BACKSPACE',
|
||||
'9': 'TAB',
|
||||
'13': 'ENTER',
|
||||
'46': 'DELETE',
|
||||
});
|
||||
|
||||
var iPhone = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
|
||||
|
||||
var KeyboardPlugin = AbstractPlugin.extend({
|
||||
events: {
|
||||
'summernote.keydown': '_onKeydown',
|
||||
'DOMNodeInserted .note-editable': '_removeGarbageSpans',
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
|
||||
events: iPhone ?
|
||||
{
|
||||
'DOMNodeInserted .note-editable': '_removeGarbageSpans',
|
||||
} :
|
||||
{
|
||||
'summernote.keydown': '_onKeydown',
|
||||
'DOMNodeInserted .note-editable': '_removeGarbageSpans',
|
||||
'keypress .note-editable': '_onKeyPress',
|
||||
'textInput .note-editable': '_onTextInput',
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -1014,76 +1023,40 @@ var KeyboardPlugin = AbstractPlugin.extend({
|
||||
* @returns {Boolean} true if case handled
|
||||
*/
|
||||
_onKeydown: function (se, e) {
|
||||
var self = this;
|
||||
var handled = false;
|
||||
|
||||
if (e.ctrlKey && e.key === 'a') {
|
||||
e.preventDefault();
|
||||
this._selectAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key &&
|
||||
(e.key.length === 1 || e.key === "Dead" || e.key === "Unidentified") &&
|
||||
!e.ctrlKey && !e.altKey && !e.metaKey) {
|
||||
|
||||
if (e.key === "Dead" || e.key === "Unidentified") {
|
||||
this._accented = true;
|
||||
}
|
||||
|
||||
// Record undo only if either:
|
||||
clearTimeout(this.lastCharIsVisibleTime);
|
||||
// e.key is punctuation or space
|
||||
var stopChars = [' ', ',', ';', ':', '?', '.', '!'];
|
||||
if (stopChars.indexOf(e.key) !== -1) {
|
||||
this.lastCharVisible = false;
|
||||
}
|
||||
// or not on top of history stack (record undo after undo)
|
||||
var history = this.context.invoke('HistoryPlugin.getHistoryStep');
|
||||
if (history && history.stack.length && history.stackOffset < history.stack.length - 1) {
|
||||
this.lastCharVisible = false;
|
||||
}
|
||||
// or no new char for 500ms
|
||||
this.lastCharIsVisibleTime = setTimeout(function () {
|
||||
self.lastCharIsVisible = false;
|
||||
}, 500);
|
||||
if (!this.lastCharIsVisible) {
|
||||
this.lastCharIsVisible = true;
|
||||
this.context.invoke('HistoryPlugin.recordUndo');
|
||||
}
|
||||
|
||||
if (e.key !== "Dead") {
|
||||
this._onVisibleChar(e, this._accented);
|
||||
}
|
||||
} else {
|
||||
this.lastCharIsVisible = false;
|
||||
this.context.invoke('editor.clearTarget');
|
||||
this.context.invoke('MediaPlugin.hidePopovers');
|
||||
this.context.invoke('editor.beforeCommand');
|
||||
switch (e.keyCode) {
|
||||
case 8: // BACKSPACE
|
||||
handled = this._onBackspace(e);
|
||||
break;
|
||||
case 9: // TAB
|
||||
handled = this._onTab(e);
|
||||
break;
|
||||
case 13: // ENTER
|
||||
handled = this._onEnter(e);
|
||||
break;
|
||||
case 46: // DELETE
|
||||
handled = this._onDelete(e);
|
||||
break;
|
||||
}
|
||||
if (handled) {
|
||||
this._preventTextInEditableDiv();
|
||||
this.context.invoke('editor.saveRange');
|
||||
e.preventDefault();
|
||||
this.context.invoke('editor.afterCommand');
|
||||
}
|
||||
if (!keycode[e.keyCode]) {
|
||||
return;
|
||||
}
|
||||
if (e.key !== "Dead") {
|
||||
this._accented = false;
|
||||
|
||||
this.context.invoke('editor.clearTarget');
|
||||
this.context.invoke('MediaPlugin.hidePopovers');
|
||||
this.context.invoke('editor.beforeCommand');
|
||||
var handled = false;
|
||||
switch (keycode[e.keyCode]) {
|
||||
case 'BACKSPACE':
|
||||
handled = this._onBackspace(e);
|
||||
break;
|
||||
case 'TAB':
|
||||
handled = this._onTab(e);
|
||||
break;
|
||||
case 'ENTER':
|
||||
handled = this._onEnter(e);
|
||||
break;
|
||||
case 'DELETE':
|
||||
handled = this._onDelete(e);
|
||||
break;
|
||||
}
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
this._preventTextInEditableDiv();
|
||||
this.context.invoke('editor.saveRange');
|
||||
this.context.invoke('editor.afterCommand');
|
||||
}
|
||||
return handled;
|
||||
},
|
||||
/**
|
||||
* Handle BACKSPACE keydown event.
|
||||
@@ -1223,45 +1196,33 @@ var KeyboardPlugin = AbstractPlugin.extend({
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Handle visible char keydown event.
|
||||
* Handle char input.
|
||||
*
|
||||
* @private
|
||||
* @param {jQueryEvent} e
|
||||
* @returns {Boolean} true if case is handled and event default must be prevented
|
||||
*/
|
||||
_onVisibleChar: function (e, accented) {
|
||||
var self = this;
|
||||
_onKeyPress: function (e) {
|
||||
console.log(e.originalEvent);
|
||||
e.preventDefault();
|
||||
if (accented) {
|
||||
this.editable.normalize();
|
||||
var baseRange = this.context.invoke('editor.createRange');
|
||||
this.context.invoke('HelperPlugin.insertTextInline', e.key);
|
||||
},
|
||||
/**
|
||||
* Handle char input.
|
||||
*
|
||||
* @private
|
||||
* @param {jQueryEvent} e
|
||||
*/
|
||||
_onTextInput: function (e) {
|
||||
e.originalEvent.preventDefault();
|
||||
|
||||
var $parent = $(baseRange.sc.parentNode);
|
||||
var parentContenteditable = $parent.attr('contenteditable');
|
||||
$parent.attr('contenteditable', false);
|
||||
|
||||
var accentPlaceholder = this.document.createElement('span');
|
||||
$(baseRange.sc).after(accentPlaceholder);
|
||||
$(accentPlaceholder).attr('contenteditable', true);
|
||||
|
||||
var range = this.context.invoke('editor.setRange', accentPlaceholder, 0);
|
||||
range.select();
|
||||
|
||||
setTimeout(function () {
|
||||
var accentedChar = accentPlaceholder.innerHTML;
|
||||
$(accentPlaceholder).remove();
|
||||
if (parentContenteditable) {
|
||||
$parent.attr('contenteditable', parentContenteditable);
|
||||
} else {
|
||||
$parent.removeAttr('contenteditable');
|
||||
}
|
||||
baseRange.select();
|
||||
self.context.invoke('HelperPlugin.insertTextInline', accentedChar);
|
||||
});
|
||||
} else {
|
||||
this.context.invoke('HelperPlugin.insertTextInline', e.key);
|
||||
var text = e.originalEvent.data;
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
if (text === 'Spacebar') {
|
||||
text = ' ';
|
||||
}
|
||||
this.context.invoke('HelperPlugin.insertTextInline', text);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -494,9 +494,10 @@ var testKeyboard = function ($editable, assert, keyboardTests, addTests) {
|
||||
keypress.keyCode = keypress.keyCode;
|
||||
var event = $.Event("keydown", keypress);
|
||||
$target.trigger(event);
|
||||
|
||||
if (!event.isDefaultPrevented()) {
|
||||
if (keypress.key.length === 1) {
|
||||
document.execCommand("insertText", 0, keypress.key);
|
||||
textInput($target[0], keypress.key);
|
||||
} else {
|
||||
console.warn('Native "' + keypress.key + '" is not supported in test');
|
||||
}
|
||||
@@ -759,8 +760,40 @@ var keydown = function (key, $editable, options) {
|
||||
var $target = $(target.tagName ? target : target.parentNode);
|
||||
var event = $.Event("keydown", keyPress);
|
||||
$target.trigger(event);
|
||||
|
||||
if (!event.isDefaultPrevented()) {
|
||||
if (keyPress.key.length === 1) {
|
||||
textInput($target[0], keyPress.key);
|
||||
} else {
|
||||
console.warn('Native "' + keyPress.key + '" is not supported in test');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var textInput = function (target, char) {
|
||||
var ev = new CustomEvent('textInput', {
|
||||
bubbles: true,
|
||||
cancelBubble: false,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
data: char,
|
||||
defaultPrevented: false,
|
||||
detail: 0,
|
||||
eventPhase: 3,
|
||||
isTrusted: true,
|
||||
returnValue: true,
|
||||
sourceCapabilities: null,
|
||||
type: "textInput",
|
||||
which: 0,
|
||||
});
|
||||
ev.data = char;
|
||||
target.dispatchEvent(ev);
|
||||
|
||||
if (!ev.defaultPrevented) {
|
||||
document.execCommand("insertText", 0, ev.data);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
wysiwygData: wysiwygData,
|
||||
createWysiwyg: createWysiwyg,
|
||||
|
||||
Reference in New Issue
Block a user