summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorNikunj Ladava <nla@odoo.com>2017-05-19 17:48:45 +0530
committerGéry Debongnie <ged@odoo.com>2018-01-25 12:18:10 +0100
commitc778de44c7cd9b7ef5a9a91738164ab0ebd0ca06 (patch)
treef41e5e5eaa1bb7e19a4ab052aa7ed65c9bb28c05 /addons
parentf50c6b1f6c79e766add1015a393230b525f89bfa (diff)
[IMP] mail : display mentions in green color in edit mode
When someone mentions another person in discuss/chat window, the mentioned name was displayed just as a text, which may be unclear. With this commit, we display mentions with the same style as mentions in a message. To do that, the main idea is to change the input by a div with contenteditable set to true. This allows us to add <a>...</a> inside.
Diffstat (limited to 'addons')
-rw-r--r--addons/mail/static/src/js/chat_window.js4
-rw-r--r--addons/mail/static/src/js/chatter.js2
-rw-r--r--addons/mail/static/src/js/chatter_composer.js2
-rw-r--r--addons/mail/static/src/js/composer.js256
-rw-r--r--addons/mail/static/src/less/composer.less83
-rw-r--r--addons/mail/static/src/less/extended_chat_window.less8
-rw-r--r--addons/mail/static/src/xml/composer.xml12
-rw-r--r--addons/mail/static/tests/chatter_tests.js16
8 files changed, 240 insertions, 143 deletions
diff --git a/addons/mail/static/src/js/chat_window.js b/addons/mail/static/src/js/chat_window.js
index 0c3a2270045..0f728e00ba1 100644
--- a/addons/mail/static/src/js/chat_window.js
+++ b/addons/mail/static/src/js/chat_window.js
@@ -132,13 +132,13 @@ return Widget.extend({
event.stopPropagation(); // to prevent jquery's blockUI to cancel event
// ENTER key (avoid requiring jquery ui for external livechat)
if (event.which === 13) {
- var content = _.str.trim(this.$input.val());
+ var content = _.str.trim(this.$input.text());
var message = {
content: content,
attachment_ids: [],
partner_ids: [],
};
- this.$input.val('');
+ this.$input.text('');
if (content) {
this.trigger('post_message', message, this.channel_id);
}
diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js
index cc71046039d..649a7381f7c 100644
--- a/addons/mail/static/src/js/chatter.js
+++ b/addons/mail/static/src/js/chatter.js
@@ -185,7 +185,7 @@ var Chatter = Widget.extend(chat_mixin, {
input_baseline: 14,
is_log: options && options.is_log,
record_name: this.record_name,
- default_body: old_composer && old_composer.$input && old_composer.$input.val(),
+ default_body: old_composer && old_composer.$input && old_composer.$input.html(),
default_mention_selections: old_composer && old_composer.mention_get_listener_selections(),
});
this.composer.on('input_focused', this, function () {
diff --git a/addons/mail/static/src/js/chatter_composer.js b/addons/mail/static/src/js/chatter_composer.js
index e22e3a5ca9f..fe23bc528fe 100644
--- a/addons/mail/static/src/js/chatter_composer.js
+++ b/addons/mail/static/src/js/chatter_composer.js
@@ -212,7 +212,7 @@ var ChatterComposer = composer.BasicComposer.extend({
recipient_done.then(function (partner_ids) {
var context = {
default_parent_id: self.id,
- default_body: utils.get_text2html(self.$input.val()),
+ default_body: utils.get_text2html(self.$input.html()),
default_attachment_ids: _.pluck(self.get('attachment_ids'), 'id'),
default_partner_ids: partner_ids,
default_is_log: self.options.is_log,
diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js
index 744e72742e2..2e2aaa66f74 100644
--- a/addons/mail/static/src/js/composer.js
+++ b/addons/mail/static/src/js/composer.js
@@ -8,7 +8,6 @@ var utils = require('mail.utils');
var config = require('web.config');
var core = require('web.core');
var data = require('web.data');
-var dom = require('web.dom');
var session = require('web.session');
var Widget = require('web.Widget');
@@ -31,7 +30,7 @@ var MentionManager = Widget.extend({
events: {
"mouseover .o_mention_proposition": "on_hover_mention_proposition",
- "click .o_mention_proposition": "on_click_mention_item",
+ "click .o_mention_proposition": "_onClickMentionItem",
},
init: function (parent, options) {
@@ -54,49 +53,6 @@ var MentionManager = Widget.extend({
this.$('.o_mention_proposition').removeClass('active');
$elem.addClass('active');
},
- on_click_mention_item: function (event) {
- event.preventDefault();
-
- var text_input = this.composer.$input.val();
- var id = $(event.currentTarget).data('id');
- var selected_suggestion = _.find(_.flatten(this.get('mention_suggestions')), function (s) {
- return s.id === id;
- });
- var substitution = selected_suggestion.substitution;
- if (!substitution) { // no substitution string given, so use the mention name instead
- // replace white spaces with non-breaking spaces to facilitate mentions detection in text
- selected_suggestion.name = selected_suggestion.name.replace(/ /g, NON_BREAKING_SPACE);
- substitution = this.active_listener.delimiter + selected_suggestion.name;
- }
- var get_mention_index = function (matches, cursor_position) {
- for (var i=0; i<matches.length; i++) {
- if (cursor_position <= matches[i].index) {
- return i;
- }
- }
- return i;
- };
-
- // add the selected suggestion to the list
- if (this.active_listener.selection.length) {
- // get mention matches (ordered by index in the text)
- var matches = this._get_match(text_input, this.active_listener);
- var index = get_mention_index(matches, this._get_selection_positions().start);
- this.active_listener.selection.splice(index, 0, selected_suggestion);
- } else {
- this.active_listener.selection.push(selected_suggestion);
- }
-
- // update input text, and reset dropdown
- var cursor_position = this._get_selection_positions().start;
- var text_left = text_input.substring(0, cursor_position-(this.mention_word.length+1));
- var text_right = text_input.substring(cursor_position, text_input.length);
- var text_input_new = text_left + substitution + ' ' + text_right;
- this.composer.$input.val(text_input_new);
- this._set_cursor_position(text_left.length+substitution.length+2);
- this.set('mention_suggestions', []);
- this.composer.focus(); // to trigger autoresize
- },
// Public API
/**
@@ -131,8 +87,8 @@ var MentionManager = Widget.extend({
get_listener_selection: function (delimiter) {
var listener = _.findWhere(this.listeners, {delimiter: delimiter});
if (listener) {
- var input_mentions = this.composer.$input.val().match(new RegExp(delimiter+'[^ ]+(?= |&nbsp;)', 'g'));
- return this._validate_selection(listener.selection, input_mentions);
+ var inputMentions = this.composer.$input.text().match(new RegExp(delimiter+'[^ ]+(?= |&nbsp;)', 'g'));
+ return this._validate_selection(listener.selection, inputMentions);
}
return [];
},
@@ -169,15 +125,15 @@ var MentionManager = Widget.extend({
*/
detect_delimiter: function () {
var self = this;
- var text_val = this.composer.$input.val();
- var cursor_position = this._get_selection_positions().start;
- var left_string = text_val.substring(0, cursor_position);
+ var textVal = this.composer.$input.html();
+ var cursorPosition = this._getSelectionPositions();
+ var leftString = textVal.substring(0, cursorPosition);
function validate_keyword (delimiter, beginning_only) {
- var delimiter_position = left_string.lastIndexOf(delimiter) - 1;
+ var delimiter_position = leftString.lastIndexOf(delimiter) - 1;
if (beginning_only && delimiter_position > 0) {
return false;
}
- var search_str = text_val.substring(delimiter_position, cursor_position);
+ var search_str = textVal.substring(delimiter_position, cursorPosition);
var pattern = "(^"+delimiter+"|(^\\s"+delimiter+"))";
var regex_start = new RegExp(pattern, "g");
search_str = search_str.replace(/^\s\s*|^[\n\r]/g, '');
@@ -263,6 +219,8 @@ var MentionManager = Widget.extend({
// Private functions
/**
* Returns the matches (as RexExp.exec does) for the mention in the input text
+ *
+ * @private
* @param {String} input_text: the text to search matches
* @param {Object} listener: the listener for which we want to find a match
* @returns {Object[]} matches in the same format as RexExp.exec()
@@ -286,6 +244,32 @@ var MentionManager = Widget.extend({
}
return result;
},
+ /**
+ * Get cursor position in contenteditable div with it's inner html content.
+ *
+ * @private
+ * @return a current cursor position
+ */
+ _getSelectionPositions: function () {
+ if (window.getSelection && window.getSelection().getRangeAt) {
+ var range = window.getSelection().getRangeAt(0);
+ var selectedObj = window.getSelection();
+ var rangeCount = 0;
+ var childNodes = selectedObj.anchorNode.parentNode.childNodes;
+ for (var i = 0; i < childNodes.length; i++) {
+ if (childNodes[i] === selectedObj.anchorNode) {
+ break;
+ }
+ if (childNodes[i].outerHTML)
+ rangeCount += childNodes[i].outerHTML.length;
+ else if (childNodes[i].nodeType === 3) {
+ rangeCount += childNodes[i].textContent.length;
+ }
+ }
+ return range.startOffset + rangeCount;
+ }
+ return -1;
+ },
_render_suggestions: function () {
var suggestions = [];
if (_.find(this.get('mention_suggestions'), _.isArray)) {
@@ -318,9 +302,23 @@ var MentionManager = Widget.extend({
this.open = false;
}
},
- _validate_selection: function (selection, input_mentions) {
+ /*
+ * Set cursor position after mention is added.
+ * @private
+ * @paran {int} node - id of childnode to set cursor position after it.
+ */
+ _setCursorPosition: function (nodeID) {
+ var range = document.createRange();
+ var selection = window.getSelection();
+ range.setStart(document.getElementById(nodeID).nextSibling, 1);
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ this.composer.$input.focus();
+ },
+ _validate_selection: function (selection, inputMentions) {
var validated_selection = [];
- _.each(input_mentions, function (mention) {
+ _.each(inputMentions, function (mention) {
var validated_mention = _.findWhere(selection, {name: mention.slice(1)});
if (validated_mention) {
validated_selection.push(validated_mention);
@@ -329,15 +327,60 @@ var MentionManager = Widget.extend({
return validated_selection;
},
- // Cursor position and selection utils
- _get_selection_positions: function () {
- var el = this.composer.$input.get(0);
- return el ? dom.getSelectionRange(el) : {start: 0, end: 0};
- },
- _set_cursor_position: function (pos) {
- this.composer.$input.each(function (index, elem) {
- dom.setSelectionRange(elem, {start: pos, end: pos});
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * after click on mention item it set to a contenteditable div with it's new
+ * cursor postion
+ *
+ * @private
+ * @param {KeyboardEvent} event - to get a current selected mention from
+ * list and eventlistener.
+ */
+ _onClickMentionItem: function (event) {
+ event.preventDefault();
+
+ var textInput = this.composer.$input.html();
+ var id = $(event.currentTarget).data('id');
+ var selectedSuggestion = _.find(_.flatten(this.get('mention_suggestions')), function (s) {
+ return s.id === id;
});
+ var substitution = selectedSuggestion.substitution;
+ if (!substitution) { // no substitution string given, so use the mention name instead
+ // replace white spaces with non-breaking spaces to facilitate mentions detection in text
+ selectedSuggestion.name = selectedSuggestion.name.replace(/ /g, NON_BREAKING_SPACE);
+ substitution = _.escape(this.active_listener.delimiter + selectedSuggestion.name);
+ }
+ var getMentionIndex = function (matches, cursorPosition) {
+ for (var i=0; i<matches.length; i++) {
+ if (cursorPosition <= matches[i].index) {
+ return i;
+ }
+ }
+ return i;
+ };
+
+ // add the selected suggestion to the list
+ if (this.active_listener.selection.length) {
+ // get mention matches (ordered by index in the text)
+ var matches = this._get_match(textInput, this.active_listener);
+ var index = getMentionIndex(matches, this._getSelectionPositions());
+ this.active_listener.selection.splice(index, 0, selectedSuggestion);
+ } else {
+ this.active_listener.selection.push(selectedSuggestion);
+ }
+
+ // update input text, and reset dropdown
+ var cursorPosition = this._getSelectionPositions();
+ var textLeft = textInput.substring(0, cursorPosition-(this.mention_word.length+1));
+ var textRight = textInput.substring(cursorPosition, textInput.length);
+ var nodeID = _.uniqueId('node');
+ var newTextInput = textLeft + "<a id="+ nodeID+">" + substitution + "</a> " + textRight;
+ this.composer.$input.html(newTextInput);
+ this._setCursorPosition(nodeID);
+ this.set('mention_suggestions', []);
},
});
@@ -345,8 +388,8 @@ var MentionManager = Widget.extend({
var BasicComposer = Widget.extend(chat_mixin, {
template: "mail.ChatComposer",
events: {
- "keydown .o_composer_input textarea": "on_keydown",
- "keyup .o_composer_input": "on_keyup",
+ "keydown .o_composer_input": "_onKeydown",
+ "keyup .o_composer_input": "_onKeyup",
"change input.o_input_file": "on_attachment_change",
"click .o_composer_button_send": "send_message",
"click .o_composer_button_add_attachment": "on_click_add_attachment",
@@ -357,6 +400,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
'focusout .o_composer_button_emoji': '_onEmojiButtonFocusout',
'focus .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageFocus',
'click .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageClick',
+ 'input .o_composer_input': '_removeMention',
},
// RPCs done to fetch the mention suggestions are throttled with the following value
MENTION_THROTTLE: 200,
@@ -433,12 +477,12 @@ var BasicComposer = Widget.extend(chat_mixin, {
this.$attachment_button = this.$(".o_composer_button_add_attachment");
this.$attachments_list = this.$('.o_composer_attachments_list');
- this.$input = this.$('.o_composer_input textarea');
+ this.$input = this.$('.o_composer_input');
this.$input.focus(function () {
self.trigger('input_focused');
});
- this.$input.val(this.options.default_body);
- dom.autoresize(this.$input, {parent: this, min_height: this.options.input_min_height});
+ this.$input.html(this.options.default_body);
+ this.$input.css('min-height', this.options.input_min_height)
// Attachments
$(window).on(this.fileupload_id, this.on_attachment_loaded);
@@ -460,7 +504,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
// behavior for the chatter composer
//Removing unwanted extra spaces from message
- var value = _.escape(this.$input.val()).trim();
+ var value = _.escape(this.$input.text()).trim();
value = value.replace(/(\r|\n){2,}/g, '<br/><br/>');
value = value.replace(/(\r|\n)/g, '<br/>');
@@ -491,7 +535,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
clear_composer: function() {
// Empty input, selected partners and attachments
- this.$input.val('');
+ this.$input.html('');
this.mention_manager.reset_selections();
this.set('attachment_ids', []);
},
@@ -503,7 +547,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
getState: function () {
return {
attachments: this.get('attachment_ids'),
- text: this.$input.val(),
+ text: this.$input.html(),
};
},
@@ -515,7 +559,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
setState: function (state) {
this.set('attachment_ids', state.attachments);
- this.$input.val(state.text);
+ this.$input.html(state.text);
},
/**
@@ -524,8 +568,18 @@ var BasicComposer = Widget.extend(chat_mixin, {
should_send: function (event) {
return !event.shiftKey;
},
-
- on_keydown: function (event) {
+ /**
+ * _onKeydown event is triggered when is key is pressed
+ * - on UP and DOWN arrow is pressed then event prevents it's default
+ * behaviour if mention manager is open else it break it.
+ * - on ENTER key is pressed and mention manager is open then event
+ * prevents it's default behaviour else check if ControlKey is
+ * pressed or not if yes then it send a message
+ *
+ * @private
+ * @param {KeyboardEvent} event
+ */
+ _onKeydown: function (event) {
switch(event.which) {
// UP, DOWN: prevent moving cursor if navigation in mention propositions
case $.ui.keyCode.UP:
@@ -549,7 +603,16 @@ var BasicComposer = Widget.extend(chat_mixin, {
}
},
- on_keyup: function (event) {
+ /**
+ * _onKeyup event is triggered when key is released.
+ * on ESCAP key it close the mention suggestion dropdown menu.
+ * on ENTER key it selects that mention and add to a content editable div.
+ * on UP or DOWN key it highlights previous or next mention respectively.
+ *
+ * @private
+ * @param {KeyboardEvent} event
+ */
+ _onKeyup: function (event) {
switch(event.which) {
// ESCAPED KEYS: do nothing
case $.ui.keyCode.END:
@@ -663,6 +726,12 @@ var BasicComposer = Widget.extend(chat_mixin, {
attachments: this.get('attachment_ids'),
}));
},
+ // remove mention when user try to edit or remove it.
+ _removeMention: function (event) {
+ if (window.getSelection().anchorNode.parentNode.tagName == 'A') {
+ document.getElementById(window.getSelection().anchorNode.parentNode.id).remove();
+ }
+ },
// Mention
mention_fetch_throttled: function (model, method, kwargs) {
@@ -748,8 +817,14 @@ var BasicComposer = Widget.extend(chat_mixin, {
},
// Others
+ /**
+ * used to check if contenteditable div is empty or not
+ *
+ * @return - it returns length of attachment and trim content of input box
+ * tp check for empty.
+ */
is_empty: function () {
- return !this.$input.val().trim() && !this.$('.o_attachments').children().length;
+ return !this.$input.text().trim() && !this.$('.o_attachments').children().length;
},
focus: function () {
this.$input.focus();
@@ -807,7 +882,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
if (this.$emojisContainer.parent().length) {
this._hideEmojis();
} else {
- this.$emojisContainer.appendTo(this.$('.o_composer'));
+ this.$emojisContainer.appendTo(this.$('.o_composer_container'));
}
},
/**
@@ -830,6 +905,31 @@ var BasicComposer = Widget.extend(chat_mixin, {
clearTimeout(this._hideEmojisTimeout);
},
/**
+ * set cursor position at the end in input box
+ *
+ * @todo move that kind of code in a utility file. Maybe dom.js
+ *
+ * @private
+ * @param {Element} el
+ */
+ _placeCaretAtEnd: function (el) {
+ el.focus();
+ if (typeof window.getSelection !== "undefined"
+ && typeof document.createRange !== "undefined") {
+ var range = document.createRange();
+ range.selectNodeContents(el);
+ range.collapse(false);
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ } else if (typeof document.body.createTextRange !== "undefined") {
+ var textRange = document.body.createTextRange();
+ textRange.moveToElementText(el);
+ textRange.collapse(false);
+ textRange.select();
+ }
+ },
+ /**
* Called when an emoji is clicked -> adds it in the <input/>, focuses the
* <input/> and closes the emoji panel.
*
@@ -837,8 +937,8 @@ var BasicComposer = Widget.extend(chat_mixin, {
* @param {Event} ev
*/
_onEmojiImageClick: function (ev) {
- this.$input.val(this.$input.val() + " " + $(ev.currentTarget).data('emoji') + " ");
- this.$input.focus();
+ this.$input.html(this.$input.html() + " " + $(ev.currentTarget).data('emoji') + " ");
+ this._placeCaretAtEnd(this.$input[0])
this._hideEmojis();
},
});
diff --git a/addons/mail/static/src/less/composer.less b/addons/mail/static/src/less/composer.less
index ec3b156af2a..9df25c895a3 100644
--- a/addons/mail/static/src/less/composer.less
+++ b/addons/mail/static/src/less/composer.less
@@ -10,7 +10,7 @@
position: relative;
background: white;
- textarea, input {
+ input {
border: none;
}
}
@@ -27,9 +27,15 @@
}
.o_composer {
- .o_composer_input > textarea {
- width: 100%;
- resize: none;
+ .o_composer_input[contenteditable=true]:empty:before {
+ content: attr(placeholder);
+ opacity: 0.75;
+ }
+ .o_composer_input {
+ display: inline-block;
+ height: 100%;
+ white-space: pre-wrap;
+ word-break: break-all;
}
.o_composer_mention_dropdown .o_mention_proposition > a {
.o-text-overflow();
@@ -58,17 +64,16 @@
color: @odoo-brand-optional;
}
}
+ }
- .o_mail_emoji_container {
- padding: 0 @o-mail-chatter-gap @o-mail-chatter-gap 0;
- border: 1px solid @gray-lighter-dark;
- border-width: 1px 0 0 0;
- background-color: @gray-lighter;
+ .o_mail_emoji_container {
+ padding: 0 @o-mail-chatter-gap @o-mail-chatter-gap 0;
+ border: 1px solid @gray-lighter-dark;
+ background-color: @gray-lighter;
- .o_mail_emoji {
- margin: @o-mail-chatter-gap 0 0 @o-mail-chatter-gap;
- cursor: pointer; // only in the popover
- }
+ .o_mail_emoji {
+ margin: @o-mail-chatter-gap 0 0 @o-mail-chatter-gap;
+ cursor: pointer; // only in the popover
}
}
@@ -94,19 +99,13 @@
.o_composer {
border-radius: 2px;
- textarea {
- padding-right: @grid-gutter-width; // avoid overlaps betwenn textarea and o_composer_button_full_composer
- }
- .o_mail_emoji_container {
- margin: 0 -@o-mail-chatter-gap;
- }
}
.o_composer, .o_composer_subject {
border: 1px solid @gray-lighter-dark;
- textarea, input {
+ input {
padding: 0 0 @o-mail-chatter-gap;
border: none;
}
@@ -118,6 +117,9 @@
.o_chatter_composer_tools {
border-top: 1px solid @gray-lighter-dark;
+ @media (max-width: @screen-xs-max) {
+ margin-top: 10px;
+ }
}
&:not(.o_chat_inline_composer) {
@@ -136,26 +138,26 @@
&.o_chat_inline_composer {
.o_composer_container {
- .o-flex(1, 0, auto);
+ .o-flex(1, 1, auto);
}
.o_composer {
+ display: flex;
+ min-height: 40px;
padding: @o-mail-chatter-gap @o-mail-chatter-gap 0 @o-mail-chatter-gap;
border-radius: 2px 0 0 2px;
border-right-width: 0;
-
- .o_mail_emoji_container {
- text-align: right;
- border-right-width: 1px;
- }
+ }
+ .o_mail_emoji_container {
+ text-align: right;
+ border-width: 1px;
}
.o_composer_input {
- .o-flex-display();
.o-flex-flow(row, wrap);
}
.o_composer_text_field {
.o-flex(1, 1, 50%);
- margin: 0;
- padding: 0;
+ margin: -4px 7px 5px 0px;
+ padding: 2.5px 0px 0px 5px;
}
.o_chatter_composer_tools {
.o-flex(0, 1, auto);
@@ -187,32 +189,25 @@
border-top: 1px solid @gray-lighter-dark;
width: 100%;
}
- .o_composer {
- .o_mail_emoji_container {
- .o-position-absolute(auto, 0, 100%, 0);
- border-bottom-width: 1px;
- }
+ .o_mail_emoji_container {
+ .o-position-absolute(auto, 0, 100%, 0);
+ border-bottom-width: 1px;
}
.o_composer_input {
- .o-flex-display();
.o-flex-flow(row, nowrap);
- textarea.o_composer_text_field {
- .o-flex(1, 1, auto);
- }
-
.o_chatter_composer_tools {
.o-flex(0, 0, auto);
}
- @media (max-width: @screen-xs-max) {
- .o_composer_button_send {
- display: inline-block;
- }
- }
}
.o_composer_button_send, .o_chatter_avatar {
display: none;
}
+ @media (max-width: @screen-xs-max) {
+ .o_composer_button_send {
+ display: inline-block;
+ }
+ }
}
}
diff --git a/addons/mail/static/src/less/extended_chat_window.less b/addons/mail/static/src/less/extended_chat_window.less
index 3ae73dee861..fcf92cf052a 100644
--- a/addons/mail/static/src/less/extended_chat_window.less
+++ b/addons/mail/static/src/less/extended_chat_window.less
@@ -42,10 +42,14 @@
}
.o_chatter_composer_tools {
.o-position-absolute(@right: 0px, @top: 0px);
+
+ @media (max-width: @screen-xs-max) {
+ .o-position-absolute(@right: 6px, @top: 0px);
+ }
button {
- padding: 10px;
+ padding: 8px;
@media (max-width: @screen-xs-max) {
- padding: 13px;
+ padding: 7px 6px 0px 6px;
}
&.btn-icon {
font-size: 1.3em;
diff --git a/addons/mail/static/src/xml/composer.xml b/addons/mail/static/src/xml/composer.xml
index b46af297928..2795a0bb724 100644
--- a/addons/mail/static/src/xml/composer.xml
+++ b/addons/mail/static/src/xml/composer.xml
@@ -8,13 +8,11 @@
<input tabindex="1" type="text" placeholder="Subject" class="o_input"/>
</div>
<div t-attf-class="o_composer #{widget.extended ? 'o_extended_composer' : ''}">
- <div class="o_composer_input">
- <textarea class="o_input o_composer_text_field" tabindex="2" placeholder="Write something..."/>
- <div class="o_chatter_composer_tools">
- <button tabindex="4" class="btn btn-sm btn-icon fa fa-smile-o o_composer_button_emoji" type="button" data-toggle="popover"/>
- <button tabindex="5" class="btn btn-sm btn-icon fa fa-paperclip o_composer_button_add_attachment" type="button"/>
- <button t-if="widget.options.isMobile" tabindex="3" class="btn btn-sm btn-icon fa fa-paper-plane-o o_composer_button_send" type="button"/>
- </div>
+ <div class="o_composer_input o_composer_text_field form-control" contenteditable="true" placeholder="Write Something..."/>
+ <div class="o_chatter_composer_tools">
+ <button tabindex="4" class="btn btn-sm btn-icon fa fa-smile-o o_composer_button_emoji" type="button" data-toggle="popover"/>
+ <button tabindex="5" class="btn btn-sm btn-icon fa fa-paperclip o_composer_button_add_attachment" type="button"/>
+ <button t-if="widget.options.isMobile" tabindex="3" class="btn btn-sm btn-icon fa fa-paper-plane-o o_composer_button_send" type="button"/>
</div>
</div>
<div class="o_composer_attachments_list"/>
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index 9711c4a0c81..0e52ba16c20 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -403,7 +403,7 @@ QUnit.test('kanban activity widget with an activity', function (assert) {
QUnit.test('chatter: post, receive and star messages', function (assert) {
var done = assert.async();
- assert.expect(27);
+ assert.expect(28);
// Remove the mention throttle to speed up the test
var mentionThrottle = BasicComposer.prototype.MENTION_THROTTLE;
@@ -505,7 +505,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
// send a message
form.$('.o_chatter_button_new_message').click();
assert.ok(!$('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be opened");
- form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
+ form.$('.oe_chatter .o_composer_text_field:first()').text("My first message");
form.$('.oe_chatter .o_composer_button_send').click();
assert.ok($('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be closed");
assert.strictEqual(form.$('.o_thread_message').length, 2, "thread should contain two messages");
@@ -519,7 +519,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
// log a note
form.$('.o_chatter_button_log_note').click();
assert.ok(!$('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be opened");
- form.$('.oe_chatter .o_composer_text_field:first()').val("My first note");
+ form.$('.oe_chatter .o_composer_text_field:first()').text("My first note");
form.$('.oe_chatter .o_composer_button_send').click();
assert.ok($('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be closed");
assert.strictEqual(form.$('.o_thread_message').length, 3, "thread should contain three messages");
@@ -545,10 +545,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
// very basic test of mention
form.$('.o_chatter_button_new_message').click();
var $input = form.$('.oe_chatter .o_composer_text_field:first()');
- $input.val('@');
- // the cursor position must be set for the mention manager to detect that we are mentionning
- $input[0].selectionStart = 1;
- $input[0].selectionEnd = 1;
+ $input.text('@');
$input.trigger('keyup');
assert.strictEqual(getSuggestionsDef.state(), "pending",
@@ -566,6 +563,9 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
assert.strictEqual(form.$('.o_mention_proposition .o_mention_info').text(), '(test@odoo.com)',
"suggestion should be displayed correctly");
+ $(".o_mention_proposition > a").trigger("click");
+ assert.strictEqual($(".o_composer_input a").length, 1, "mention is 'green' in edit mode");
+
BasicComposer.prototype.MENTION_THROTTLE = mentionThrottle;
form.destroy();
done();
@@ -636,7 +636,7 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert)
// send a message
form.$('.o_chatter_button_new_message').click();
- form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
+ form.$('.oe_chatter .o_composer_text_field:first()').text("My first message");
form.$('.oe_chatter .o_composer_button_send').click();
assert.strictEqual(form.$('.o_thread_message').length, 1, "thread should contain a message");
assert.ok(form.$('.o_thread_message:first() .o_thread_message_core').text().indexOf('My first message') >= 0,