diff --git a/addons/web_editor/static/src/js/backend/field_html.js b/addons/web_editor/static/src/js/backend/field_html.js index 9f6cfaa586a..dc4d1d0af16 100644 --- a/addons/web_editor/static/src/js/backend/field_html.js +++ b/addons/web_editor/static/src/js/backend/field_html.js @@ -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, }, diff --git a/addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js b/addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js index 94ec9306f7d..534fdcab523 100644 --- a/addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js +++ b/addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js @@ -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); }, }); diff --git a/addons/web_editor/static/tests/test_utils.js b/addons/web_editor/static/tests/test_utils.js index 27ba86b52ff..1ad47212e73 100644 --- a/addons/web_editor/static/tests/test_utils.js +++ b/addons/web_editor/static/tests/test_utils.js @@ -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,