diff --git a/app/css/app.css b/app/css/app.css
index 9a07986a..e21fdbea 100644
--- a/app/css/app.css
+++ b/app/css/app.css
@@ -1915,53 +1915,7 @@ img.img_fullsize {
-/* Emoji area */
-.emoji-wysiwyg-editor:empty:before {
- content: attr(placeholder);
- color: #9aa2ab;
-}
-.emoji-wysiwyg-editor:active:before,
-.emoji-wysiwyg-editor:focus:before {
- content: none;
-}
-
-.emoji-wysiwyg-editor {
- box-sizing: content-box;
- -moz-box-sizing: content-box;
- font-size: 12px;
- margin-bottom: 10px;
- padding: 6px;
- min-height: 38px;
- height: auto;
- width: auto;
- max-height: 284px;
- overflow: auto;
- line-height: 17px;
-
- border: 1px solid #d2dbe3;
- border-radius: 2px;
- -webkit-box-shadow: none;
- box-shadow: none;
- -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
- transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
-
- -webkit-user-select: text;
- word-wrap: break-word;
-
- -webkit-user-select: text;
- -moz-user-select: text;
- -ms-user-select: text;
- user-select: text;
-}
-.emoji-wysiwyg-editor img {
- width: 20px;
- height: 20px;
- vertical-align: middle;
- margin: -3px 0 0 0;
-}
-
-
-
+/* Message composer */
.composer_emoji_insert_btn {
display: block;
position: absolute;
@@ -2001,6 +1955,18 @@ img.img_fullsize {
background: #FFF;
padding: 5px 2px 5px 5px;
}
+.composer_emoji_tooltip_shown {
+ display: block;
+}
+.composer_emoji_tooltip_tail {
+ position: absolute;
+ bottom: -14px;
+ left: 50%;
+ margin-left: -13px;
+ overflow: hidden;
+ width: 26px;
+ height: 14px;
+}
.icon-tooltip-tail {
background: #FFF;
width: 18px;
@@ -2019,10 +1985,10 @@ img.img_fullsize {
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
- position: absolute;
- bottom: -10px;
- left: 50%;
- margin-left: -9px;
+ position: relative;
+ top: -8px;
+ left: 4px;
+
}
.composer_emoji_tooltip_tabs {
@@ -2089,8 +2055,6 @@ a.composer_emoji_btn:hover {
background-color: #edf2f5;
}
-
-
.emoji {
display: -moz-inline-box;
-moz-box-orient: vertical;
@@ -2128,7 +2092,6 @@ a.composer_emoji_btn:hover {
background-image: url('../img/emojisprite_4.png');
}
-
.emoji-w20 {
width: 20px;
height: 20px;
@@ -2141,6 +2104,18 @@ a.composer_emoji_btn:hover {
.emoji-w20.emoji-spritesheet-3 {background-size: 680px 60px;}
.emoji-w20.emoji-spritesheet-4 {background-size: 680px 140px;}
+.emoji-w26 {
+ width: 26px;
+ height: 26px;
+ vertical-align: middle;
+ display: inline-block;
+}
+.emoji-w26.emoji-spritesheet-0 {background-size: 702px 182px;}
+.emoji-w26.emoji-spritesheet-1 {background-size: 754px 104px;}
+.emoji-w26.emoji-spritesheet-2 {background-size: 858px 182px;}
+.emoji-w26.emoji-spritesheet-3 {background-size: 884px 78px;}
+.emoji-w26.emoji-spritesheet-4 {background-size: 884px 182px;}
+
.composer_dropdown {
display: none;
/*max-width: 100%;*/
@@ -2186,6 +2161,55 @@ a.composer_emoji_btn:hover {
max-height: 64px;
}
+.composer_rich_textarea {
+ box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ font-size: 12px;
+ margin-bottom: 10px;
+ padding: 6px;
+ min-height: 38px;
+ height: auto;
+ width: auto;
+ max-height: 284px;
+ overflow: auto;
+ line-height: 17px;
+
+ border: 1px solid #d2dbe3;
+ border-radius: 2px;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+
+ -webkit-user-select: text;
+ word-wrap: break-word;
+
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+}
+
+.composer_rich_textarea:empty:before {
+ content: attr(placeholder);
+ color: #9aa2ab;
+}
+.composer_rich_textarea:active:before,
+.composer_rich_textarea:focus:before {
+ content: none;
+}
+.composer_rich_textarea:empty:active:before,
+.composer_rich_textarea:empty:focus:before {
+ content: attr(placeholder);
+ color: #9aa2ab;
+}
+
+.composer_rich_textarea img {
+ width: 20px;
+ height: 20px;
+ vertical-align: middle;
+ margin: -3px 0 0 0;
+}
.error_modal_window .modal-dialog {
diff --git a/app/css/desktop.css b/app/css/desktop.css
index 5bbb607f..6a7d3d4d 100644
--- a/app/css/desktop.css
+++ b/app/css/desktop.css
@@ -963,7 +963,7 @@ a.im_panel_peer_photo .peer_initials {
opacity: 1;
}
.composer_emoji_insert_btn:active .icon-emoji,
-.composer_emoji_insert_btn.on .icon-emoji {
+.composer_emoji_insert_btn.composer_emoji_insert_btn_on .icon-emoji {
background-position: -9px -367px;
opacity: 1;
}
@@ -986,8 +986,8 @@ a.im_panel_peer_photo .peer_initials {
.im_send_field_wrap {
margin-bottom: 15px;
}
-.emoji-wysiwyg-editor,
-.form-control.im_message_field {
+.composer_rich_textarea,
+.composer_textarea {
border-radius: 0;
border: 0;
box-shadow: none;
@@ -999,8 +999,8 @@ a.im_panel_peer_photo .peer_initials {
line-height: 20px;
height: auto;
}
-.emoji-wysiwyg-editor:focus,
-.form-control.im_message_field:focus {
+.composer_rich_textarea:focus,
+.composer_textarea:focus {
border: 0;
box-shadow: none;
outline: none;
diff --git a/app/css/mobile.css b/app/css/mobile.css
index e02939ec..1e84e566 100644
--- a/app/css/mobile.css
+++ b/app/css/mobile.css
@@ -1105,7 +1105,7 @@ a.mobile_modal_action .tg_checkbox_label {
display: block;
}
-.im_emoji_btn {
+.composer_emoji_insert_btn {
position: absolute;
left: 0;
top: 0;
@@ -1127,10 +1127,10 @@ a.mobile_modal_action .tg_checkbox_label {
.is_1x .icon-emoji {
background-image: url(../img/icons/IconsetW_1x.png);
}
-.im_emoji_btn:active .icon-emoji,
-.is_1x .im_emoji_btn:active .icon-emoji,
-.im_emoji_btn.on .icon-emoji,
-.is_1x .im_emoji_btn.on .icon-emoji {
+.composer_emoji_insert_btn:active .icon-emoji,
+.is_1x .composer_emoji_insert_btn:active .icon-emoji,
+.composer_emoji_insert_btn.on .icon-emoji,
+.is_1x .composer_emoji_insert_btn.on .icon-emoji {
background-position: -10px -803px;
}
@@ -1141,21 +1141,21 @@ a.mobile_modal_action .tg_checkbox_label {
.im_send_field_wrap .form-control {
border-radius: 4px;
}
-.emoji-wysiwyg-editor {
+.composer_rich_textarea {
min-height: 18px;
max-height: 136px;
margin-bottom: 0;
font-size: 15px;
}
-.emoji-wysiwyg-editor:empty {
+.composer_rich_textarea:empty {
background: #f1f1f1;
border-color: #f1f1f1;
}
-.emoji-wysiwyg-editor:empty:before {
+.composer_rich_textarea:empty:before {
color: #999;
}
-.emoji-wysiwyg-editor:active,
-.emoji-wysiwyg-editor:focus {
+.composer_rich_textarea:active,
+.composer_rich_textarea:focus {
box-shadow: none;
outline: 0;
background: #FFF;
@@ -1164,17 +1164,33 @@ a.mobile_modal_action .tg_checkbox_label {
}
-.emoji-menu {
- margin-left: -20px;
- margin-top: -202px;
+.composer_emoji_tooltip {
+ margin-left: 10px;
+ margin-top: -175px;
width: 262px;
}
-.emoji-menu .emoji-items-wrap {
+.composer_emoji_tooltip .composer_emoji_tooltip_content {
height: 106px;
}
-.emoji-menu .emoji-items a {
+.composer_emoji_tooltip .composer_emoji_tooltip_content .composer_emoji_btn {
padding: 5px;
}
+.icon-tooltip-tail {
+ display: none;
+}
+.composer_emoji_tooltip_tab {
+ margin: 0 5px;
+}
+
+.composer_sticker_btn {
+ width: 80px;
+ height: 80px;
+ padding: 3px;
+}
+.composer_sticker_image {
+ max-width: 64px;
+ max-height: 64px;
+}
.contacts_modal_search {
diff --git a/app/index.html b/app/index.html
index e98f1f7c..1164326e 100644
--- a/app/index.html
+++ b/app/index.html
@@ -48,7 +48,6 @@
-
@@ -83,8 +82,8 @@
PRODUCTION_ONLY_END-->
-
+
diff --git a/app/js/controllers.js b/app/js/controllers.js
index 333ee3e7..278efae4 100644
--- a/app/js/controllers.js
+++ b/app/js/controllers.js
@@ -1448,10 +1448,10 @@ angular.module('myApp.controllers', ['myApp.i18n'])
var text = $scope.draftMessage.text;
if (angular.isString(text) && text.length > 0) {
- text = text.replace(/:([a-z0-9\-\+\*_]+?):/gi, function (all, name) {
- var utfChar = $.emojiarea.reverseIcons[name];
- if (utfChar !== undefined) {
- return utfChar;
+ text = text.replace(/:([a-z0-9\-\+\*_]+?):/gi, function (all, shortcut) {
+ var emojiCode = EmojiHelper.shortcuts[shortcut];
+ if (emojiCode !== undefined) {
+ return EmojiHelper.emojis[emojiCode][0];
}
return all;
});
@@ -1506,10 +1506,10 @@ angular.module('myApp.controllers', ['myApp.i18n'])
var backupDraftObj = {};
backupDraftObj['draft' + $scope.curDialog.peerID] = newVal;
Storage.set(backupDraftObj);
- // console.log('draft save', backupDraftObj);
+ // console.log(dT(), 'draft save', backupDraftObj);
} else {
Storage.remove('draft' + $scope.curDialog.peerID);
- // console.log('draft delete', 'draft' + $scope.curDialog.peerID);
+ // console.log(dT(), 'draft delete', 'draft' + $scope.curDialog.peerID);
}
}
diff --git a/app/js/directives.js b/app/js/directives.js
index 0824928b..6899fc1d 100755
--- a/app/js/directives.js
+++ b/app/js/directives.js
@@ -1072,7 +1072,15 @@ angular.module('myApp.directives', ['myApp.filters'])
function link ($scope, element, attrs) {
+ var messageField = $('textarea', element)[0];
var emojiButton = $('.composer_emoji_insert_btn', element)[0];
+ var emojiPanel = $('.composer_emoji_panel', element)[0];
+ var fileSelects = $('input', element);
+ var dropbox = $('.im_send_dropbox_wrap', element)[0];
+ var messageFieldWrap = $('.im_send_field_wrap', element)[0];
+ var dragStarted, dragTimeout;
+ var submitBtn = $('.im_submit', element)[0];
+
new EmojiTooltip(emojiButton, {
getStickers: function (callback) {
AppStickersManager.getStickers().then(function () {
@@ -1093,153 +1101,77 @@ angular.module('myApp.directives', ['myApp.filters'])
}
});
- var emojiPanel = $('.composer_emoji_panel', element)[0];
- new EmojiPanel(emojiPanel, {
- onEmojiSelected: function (code) {
- composer.onEmojiSelected(code);
- }
- });
-
-
- var messageField = $('textarea', element)[0];
- var composer = new MessageComposer(messageField, {});
-
- return;
-
+ var composerEmojiPanel;
+ if (emojiPanel) {
+ composerEmojiPanel = new EmojiPanel(emojiPanel, {
+ onEmojiSelected: function (code) {
+ composer.onEmojiSelected(code);
+ }
+ });
+ }
- var messageField = $('textarea', element)[0],
- fileSelects = $('input', element),
- dropbox = $('.im_send_dropbox_wrap', element)[0],
- emojiButton = $('.im_emoji_btn', element)[0],
- emojiQuickSelect = !Config.Mobile ? $('.im_emoji_quick_select_area', element)[0] : false,
- editorElement = messageField,
- dragStarted, dragTimeout,
- emojiArea = $(messageField).emojiarea({button: emojiButton, norealTime: true, quickSelect: emojiQuickSelect}),
- emojiMenu = $('.emoji-menu', element)[0],
- submitBtn = $('.im_submit', element)[0],
- richTextarea = $('.emoji-wysiwyg-editor', element)[0];
+ var composer = new MessageComposer(messageField, {
+ onTyping: function () {
+ $scope.$emit('ui_typing');
+ },
+ getSendOnEnter: function () {
+ return sendOnEnter;
+ },
+ onMessageSubmit: onMessageSubmit,
+ onFilesPaste: onFilesPaste
+ });
+ var richTextarea = composer.richTextareaEl[0];
if (richTextarea) {
- editorElement = richTextarea;
- $(richTextarea).addClass('form-control');
- $(richTextarea).attr('placeholder', $interpolate($(messageField).attr('placeholder'))($scope));
-
- var updatePromise;
$(richTextarea)
- .on('DOMNodeInserted', onPastedImageEvent)
- .on('keyup', function (e) {
- updateHeight();
-
- if (!sendAwaiting) {
- $scope.$apply(function () {
- $scope.draftMessage.text = richTextarea.textContent;
- });
- }
-
- $timeout.cancel(updatePromise);
- updatePromise = $timeout(updateValue, 1000);
- });
+ .attr('placeholder', $interpolate($(messageField).attr('placeholder'))($scope))
+ .on('keydown keyup', updateHeight);
}
- // Head is sometimes slower
- $timeout(function () {
- fileSelects
- .on('change', function () {
- var self = this;
- $scope.$apply(function () {
- $scope.draftMessage.files = Array.prototype.slice.call(self.files);
- $scope.draftMessage.isMedia = $(self).hasClass('im_media_attach_input') || Config.Mobile;
- setTimeout(function () {
- try {
- self.value = '';
- } catch (e) {};
- }, 1000);
- });
- });
- }, 1000);
-
- var sendOnEnter = true,
- updateSendSettings = function () {
- Storage.get('send_ctrlenter').then(function (sendOnCtrl) {
- sendOnEnter = !sendOnCtrl;
- });
- };
+ fileSelects.on('change', function () {
+ var self = this;
+ $scope.$apply(function () {
+ $scope.draftMessage.files = Array.prototype.slice.call(self.files);
+ $scope.draftMessage.isMedia = $(self).hasClass('im_media_attach_input') || Config.Mobile;
+ setTimeout(function () {
+ try {
+ self.value = '';
+ } catch (e) {};
+ }, 1000);
+ });
+ });
+ var sendOnEnter = true;
+ function updateSendSettings () {
+ Storage.get('send_ctrlenter').then(function (sendOnCtrl) {
+ sendOnEnter = !sendOnCtrl;
+ });
+ };
$scope.$on('settings_changed', updateSendSettings);
updateSendSettings();
- $(editorElement).on('keydown', function (e) {
- if (richTextarea) {
- updateHeight();
- }
+ $(submitBtn).on('mousedown touchstart', onMessageSubmit);
- if (e.keyCode == 13) {
- var submit = false;
- if (sendOnEnter && !e.shiftKey) {
- submit = true;
- } else if (!sendOnEnter && (e.ctrlKey || e.metaKey)) {
- submit = true;
- }
-
- if (submit) {
- $timeout.cancel(updatePromise);
- updateValue();
- $scope.draftMessage.send();
- $(element).trigger('message_send');
- resetTyping();
- return cancelEvent(e);
+ function onMessageSubmit (e) {
+ $scope.$apply(function () {
+ updateValue();
+ $scope.draftMessage.send();
+ composer.resetTyping();
+ if (composerEmojiPanel) {
+ composerEmojiPanel.update();
}
- }
-
- });
-
- $(submitBtn).on('mousedown touchstart', function (e) {
- $timeout.cancel(updatePromise);
- updateValue();
- $scope.draftMessage.send();
- $(element).trigger('message_send');
- resetTyping();
+ });
return cancelEvent(e);
- });
-
- var lastTyping = 0,
- lastLength;
- $(editorElement).on('keyup', function (e) {
- var now = tsNow(),
- length = (editorElement[richTextarea ? 'textContent' : 'value']).length;
-
-
- if (now - lastTyping > 5000 && length != lastLength) {
- lastTyping = now;
- lastLength = length;
- $scope.$emit('ui_typing');
- }
- });
-
- function resetTyping () {
- lastTyping = 0;
- lastLength = 0;
- };
-
- function updateRichTextarea () {
- if (richTextarea) {
- $timeout.cancel(updatePromise);
- var html = $('
').text($scope.draftMessage.text || '').html();
- html = html.replace(/\n/g, '
');
- $(richTextarea).html(html);
- lastLength = html.length;
- updateHeight();
- }
}
function updateValue () {
if (richTextarea) {
- $(richTextarea).trigger('change');
+ composer.onChange();
updateHeight();
}
}
- var height = richTextarea.offsetHeight;
+ var height = richTextarea && richTextarea.offsetHeight;
function updateHeight () {
var newHeight = richTextarea.offsetHeight;
if (height != newHeight) {
@@ -1250,7 +1182,7 @@ angular.module('myApp.directives', ['myApp.filters'])
function onKeyDown(e) {
if (e.keyCode == 9 && !e.shiftKey && !e.ctrlKey && !e.metaKey && !$modalStack.getTop()) { // TAB
- editorElement.focus();
+ composer.focus();
return cancelEvent(e);
}
}
@@ -1265,13 +1197,18 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.$on('ui_history_change', focusField);
}
- $scope.$on('ui_peer_change', resetTyping);
- $scope.$on('ui_peer_draft', updateRichTextarea);
+ $scope.$on('ui_peer_change', composer.resetTyping.bind(composer));
+ $scope.$on('ui_peer_draft', function () {
+ if (richTextarea) {
+ composer.setValue($scope.draftMessage.text || '');
+ updateHeight();
+ }
+ composer.focus();
+ });
var sendAwaiting = false;
$scope.$on('ui_message_before_send', function () {
sendAwaiting = true;
- $timeout.cancel(updatePromise);
updateValue();
});
$scope.$on('ui_message_send', function () {
@@ -1279,35 +1216,17 @@ angular.module('myApp.directives', ['myApp.filters'])
focusField();
});
-
function focusField () {
onContentLoaded(function () {
- editorElement.focus();
+ composer.focus();
});
}
- function onPastedImageEvent (e) {
- var element = (e.originalEvent || e).target,
- src = (element || {}).src || '',
- remove = false;
-
- if (src.substr(0, 5) == 'data:') {
- remove = true;
- var blob = dataUrlToBlob(src);
- ErrorService.confirm({type: 'FILE_CLIPBOARD_PASTE'}).then(function () {
- $scope.draftMessage.files = [blob];
- $scope.draftMessage.isMedia = true;
- });
- setZeroTimeout(function () {
- element.parentNode.removeChild(element);
- })
- }
- else if (src && !src.match(/img\/blank\.gif/)) {
- var replacementNode = document.createTextNode(' ' + src + ' ');
- setTimeout(function () {
- element.parentNode.replaceChild(replacementNode, element);
- }, 100);
- }
+ function onFilesPaste (blobs) {
+ ErrorService.confirm({type: 'FILE_CLIPBOARD_PASTE'}).then(function () {
+ $scope.draftMessage.files = blobs;
+ $scope.draftMessage.isMedia = true;
+ });
};
function onPasteEvent (e) {
@@ -1348,7 +1267,7 @@ angular.module('myApp.directives', ['myApp.filters'])
if (e.type == 'dragenter' || e.type == 'dragover') {
if (dragStateChanged) {
$(dropbox)
- .css({height: editorElement.offsetHeight + 2, width: editorElement.offsetWidth})
+ .css({height: messageFieldWrap.offsetHeight + 2, width: messageFieldWrap.offsetWidth})
.show();
}
} else {
@@ -1371,15 +1290,11 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.$on('$destroy', function cleanup() {
- $('body').off('dragenter dragleave dragover drop', onDragDropEvent);
$(document).off('paste', onPasteEvent);
$(document).off('keydown', onKeyDown);
- $(submitBtn).off('mousedown')
+ $('body').off('dragenter dragleave dragover drop', onDragDropEvent);
+ $(submitBtn).off('mousedown touchstart');
fileSelects.off('change');
- if (richTextarea) {
- $(richTextarea).off('DOMNodeInserted keyup', onPastedImageEvent);
- }
- $(editorElement).off('keydown');
});
if (!Config.Navigator.touch) {
diff --git a/app/js/lib/utils.js b/app/js/lib/utils.js
index 77a25605..dea19f5d 100644
--- a/app/js/lib/utils.js
+++ b/app/js/lib/utils.js
@@ -112,25 +112,63 @@ function getFieldSelection (field) {
return len;
}
-function getFieldValue(field) {
+function getRichValue(field) {
if (!field) {
return '';
}
- if (field.tagName == 'INPUT' || field.tagName == 'TEXTAREA') {
- return field.value;
- }
var lines = [];
var line = [];
- getFieldElementValue(field, lines, line);
+
+ getRichElementValue(field, lines, line);
if (line.length) {
lines.push(line.join(''));
}
+
return lines.join('\n');
}
-function getFieldElementValue(node, lines, line) {
+function getRichValueWithCaret(field) {
+ if (!field) {
+ return [];
+ }
+ var lines = [];
+ var line = [];
+
+ var sel = window.getSelection ? window.getSelection() : false;
+ var selNode, selOffset;
+ if (sel && sel.rangeCount) {
+ var range = sel.getRangeAt(0);
+ if (range.startContainer &&
+ range.startContainer == range.endContainer &&
+ range.startOffset == range.endOffset) {
+ selNode = range.startContainer;
+ selOffset = range.startOffset;
+ }
+ }
+
+ getRichElementValue(field, lines, line, selNode, selOffset);
+
+ if (line.length) {
+ lines.push(line.join(''));
+ }
+
+ var value = lines.join('\n');
+ var caretPos = value.indexOf('\001');
+ if (caretPos != -1) {
+ value = value.substr(0, caretPos) + value.substr(caretPos + 1);
+ }
+
+ return [value, caretPos];
+}
+
+function getRichElementValue(node, lines, line, selNode, selOffset) {
if (node.nodeType == 3) { // TEXT
- line.push(node.nodeValue);
+ if (selNode === node) {
+ var value = node.nodeValue;
+ line.push(value.substr(0, selOffset) + '\001' + value.substr(selOffset));
+ } else {
+ line.push(node.nodeValue);
+ }
return;
}
if (node.nodeType != 1) { // NON-ELEMENT
@@ -147,9 +185,12 @@ function getFieldElementValue(node, lines, line) {
line.push(node.alt);
}
}
+ if (selNode === node) {
+ line.push('\001');
+ }
var curChild = node.firstChild;
while (curChild) {
- getFieldElementValue(curChild, lines, line);
+ getRichElementValue(curChild, lines, line, selNode, selOffset);
curChild = curChild.nextSibling;
}
if (isBlock && line.length) {
@@ -158,8 +199,31 @@ function getFieldElementValue(node, lines, line) {
}
}
+function setRichFocus(field, selectNode) {
+ field.focus();
+ if (window.getSelection && document.createRange) {
+ var range = document.createRange();
+ if (selectNode) {
+ range.selectNode(selectNode);
+ } else {
+ range.selectNodeContents(field);
+ }
+ range.collapse(false);
+
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+ else if (document.body.createTextRange !== undefined) {
+ var textRange = document.body.createTextRange();
+ textRange.moveToElementText(selectNode || field);
+ textRange.collapse(false);
+ textRange.select();
+ }
+}
+
function onContentLoaded (cb) {
- setTimeout(cb, 0);
+ setZeroTimeout(cb);
}
function tsNow (seconds) {
diff --git a/app/js/message_composer.js b/app/js/message_composer.js
index bd01a19d..e1064baa 100644
--- a/app/js/message_composer.js
+++ b/app/js/message_composer.js
@@ -147,6 +147,17 @@ function EmojiTooltip (btnEl, options) {
self.onMouseLeave(true);
}
});
+ $(this.btnEl).on('mousedown', function (e) {
+ if (!self.shown) {
+ clearTimeout(self.showTimeout);
+ delete self.showTimeout;
+ self.show();
+ } else {
+ clearTimeout(self.hideTimeout);
+ delete self.hideTimeout;
+ self.hide();
+ }
+ });
}
EmojiTooltip.prototype.onMouseEnter = function (triggerShow) {
@@ -180,7 +191,7 @@ EmojiTooltip.prototype.createTooltip = function () {
}
var self = this;
- this.tooltipEl = $('
').appendTo(document.body);
+ this.tooltipEl = $('
').appendTo(document.body);
this.tabsEl = $('.composer_emoji_tooltip_tabs', this.tooltip);
this.contentEl = $('.composer_emoji_tooltip_content', this.tooltip);
@@ -278,7 +289,7 @@ EmojiTooltip.prototype.updateTabContents = function (tab) {
emoticonData = Config.Emoji[emoticonCode];
x = iconSize * (i % totalColumns);
y = iconSize * Math.floor(i / totalColumns);
- html.push('
');
+ html.push('
');
}
this.contentEl.html(html.join(''));
}
@@ -296,7 +307,7 @@ EmojiTooltip.prototype.updateTabContents = function (tab) {
pos = spritesheet[1];
x = iconSize * spritesheet[3];
y = iconSize * spritesheet[2];
- html.push('
');
+ html.push('
');
}
}
self.contentEl.html(html.join(''));
@@ -311,13 +322,17 @@ EmojiTooltip.prototype.updatePosition = function () {
EmojiTooltip.prototype.show = function () {
this.updatePosition();
- this.tooltipEl.show();
+ this.tooltipEl.addClass('composer_emoji_tooltip_shown');
+ this.btnEl.addClass('composer_emoji_insert_btn_on');
delete this.showTimeout;
+ this.shown = true;
};
EmojiTooltip.prototype.hide = function () {
- this.tooltipEl.hide();
+ this.tooltipEl.removeClass('composer_emoji_tooltip_shown');
+ this.btnEl.removeClass('composer_emoji_insert_btn_on');
delete this.hideTimeout;
+ delete this.shown;
};
@@ -381,8 +396,6 @@ function MessageComposer (textarea, options) {
this.textareaEl = $(textarea);
this.setUpInput();
- // this.textareaEl.on('keyup keydown', this.onKeyEvent.bind(this));
- // this.textareaEl.on('focus blur', this.onFocusBlur.bind(this));
this.autoCompleteEl = $('').appendTo(document.body);
@@ -395,7 +408,7 @@ function MessageComposer (textarea, options) {
}
if (code = target.attr('data-code')) {
if (self.onEmojiSelected) {
- self.onEmojiSelected(code);
+ self.onEmojiSelected(code, true);
}
EmojiHelper.pushPopularEmoji(code);
}
@@ -403,24 +416,32 @@ function MessageComposer (textarea, options) {
});
this.isActive = false;
+
+ this.onTyping = options.onTyping;
+ this.onMessageSubmit = options.onMessageSubmit;
+ this.getSendOnEnter = options.getSendOnEnter;
}
MessageComposer.prototype.setUpInput = function () {
if ('contentEditable' in document.body) {
- this.setUpContenteditable();
+ this.setUpRich();
} else {
this.setUpPlaintext();
}
}
-MessageComposer.prototype.setUpContenteditable = function () {
+MessageComposer.prototype.setUpRich = function () {
this.textareaEl.hide();
- this.contentEditableEl = $('
');
+ this.richTextareaEl = $('
');
- this.textareaEl[0].parentNode.insertBefore(this.contentEditableEl[0], this.textareaEl[0]);
+ this.textareaEl[0].parentNode.insertBefore(this.richTextareaEl[0], this.textareaEl[0]);
- this.contentEditableEl.on('keyup keydown', this.onKeyEvent.bind(this));
- this.contentEditableEl.on('focus blur', this.onFocusBlur.bind(this));
+ this.richTextareaEl.on('keyup keydown', this.onKeyEvent.bind(this));
+ this.richTextareaEl.on('focus blur', this.onFocusBlur.bind(this));
+ this.richTextareaEl.on('paste', this.onRichPaste.bind(this));
+ this.richTextareaEl.on('DOMNodeInserted', this.onRichPasteNode.bind(this));
+
+ $(document.body).on('keydown', this.backupSelection.bind(this));
}
MessageComposer.prototype.setUpPlaintext = function () {
@@ -432,51 +453,145 @@ MessageComposer.prototype.onKeyEvent = function (e) {
var self = this;
if (e.type == 'keyup') {
this.checkAutocomplete();
- }
- if (e.type == 'keydown' && this.autocompleteShown) {
- if (e.keyCode == 38 || e.keyCode == 40) { // UP / DOWN
- var next = e.keyCode == 40;
- var currentSelected = $(this.autoCompleteEl).find('.composer_emoji_option_active');
-
- if (currentSelected.length) {
- var currentSelectedWrap = currentSelected[0].parentNode;
- var nextWrap = currentSelectedWrap[next ? 'nextSibling' : 'previousSibling'];
- currentSelected.removeClass('composer_emoji_option_active');
- if (nextWrap) {
- $(nextWrap).find('a').addClass('composer_emoji_option_active');
- return cancelEvent(e);
+
+ if (this.onTyping) {
+ var now = tsNow();
+ if (now - this.lastTyping > 5000) {
+ var length = (this.richTextareaEl ? this.richTextareaEl[0].textContent : this.textareaEl[0].value).length;
+
+ if (length != this.lastLength) {
+ this.lastTyping = now;
+ this.lastLength = length;
+ this.onTyping();
}
}
+ }
+
+ if (this.richTextareaEl) {
+ clearTimeout(this.updateValueTO);
+ var now = tsNow();
+ if (this.keyupStarted === undefined) {
+ this.keyupStarted = now;
+ }
+ if (now - this.keyupStarted > 10000) {
+ this.onChange();
+ }
+ else {
+ this.updateValueTO = setTimeout(this.onChange.bind(this), 1000);
+ }
+ }
+
+ }
+ if (e.type == 'keydown') {
+ if (this.autocompleteShown) {
+ if (e.keyCode == 38 || e.keyCode == 40) { // UP / DOWN
+ var next = e.keyCode == 40;
+ var currentSelected = $(this.autoCompleteEl).find('.composer_emoji_option_active');
+
+ if (currentSelected.length) {
+ var currentSelectedWrap = currentSelected[0].parentNode;
+ var nextWrap = currentSelectedWrap[next ? 'nextSibling' : 'previousSibling'];
+ currentSelected.removeClass('composer_emoji_option_active');
+ if (nextWrap) {
+ $(nextWrap).find('a').addClass('composer_emoji_option_active');
+ return cancelEvent(e);
+ }
+ }
+
+ var childNodes = this.autoCompleteEl[0].childNodes;
+ var nextWrap = childNodes[next ? 0 : childNodes.length - 1];
+ $(nextWrap).find('a').addClass('composer_emoji_option_active');
- var childNodes = this.autoCompleteEl[0].childNodes;
- var nextWrap = childNodes[next ? 0 : childNodes.length - 1];
- $(nextWrap).find('a').addClass('composer_emoji_option_active');
+ return cancelEvent(e);
+ }
- return cancelEvent(e);
+ if (e.keyCode == 13) { // ENTER
+ var currentSelected = $(this.autoCompleteEl).find('.composer_emoji_option_active') ||
+ $(this.autoCompleteEl).childNodes[0].find('a');
+ var code = currentSelected.attr('data-code');
+ if (code) {
+ this.onEmojiSelected(code, true);
+ EmojiHelper.pushPopularEmoji(code);
+ }
+ return cancelEvent(e);
+ }
}
- if (e.keyCode == 13) { // ENTER
- var currentSelected = $(this.autoCompleteEl).find('.composer_emoji_option_active') ||
- $(this.autoCompleteEl).childNodes[0].find('a');
- var code = currentSelected.attr('data-code');
- if (code) {
- this.onEmojiSelected(code);
- EmojiHelper.pushPopularEmoji(code);
+ else if (e.keyCode == 13) {
+ var submit = false;
+ var sendOnEnter = true;
+ if (this.getSendOnEnter && !this.getSendOnEnter()) {
+ sendOnEnter = false;
+ }
+ if (sendOnEnter && !e.shiftKey) {
+ submit = true;
+ } else if (!sendOnEnter && (e.ctrlKey || e.metaKey)) {
+ submit = true;
+ }
+
+ if (submit) {
+ this.onMessageSubmit(e);
+ return cancelEvent(e);
}
- return cancelEvent(e);
}
}
}
+MessageComposer.prototype.backupSelection = function () {
+ delete this.selection;
+
+ if (!this.isActive) {
+ return;
+ }
+ if (window.getSelection) {
+ var sel = window.getSelection();
+ if (sel.getRangeAt && sel.rangeCount) {
+ this.selection = sel.getRangeAt(0);
+ }
+ } else if (document.selection && document.selection.createRange) {
+ this.selection = document.selection.createRange();
+ }
+}
+
+MessageComposer.prototype.restoreSelection = function () {
+ if (!this.selection) {
+ return false;
+ }
+ var result = false;
+ if (window.getSelection) {
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(this.selection);
+ result = true;
+ }
+ else if (document.selection && this.selection.select) {
+ this.selection.select();
+ result = true;
+ }
+ delete this.selection;
+
+ return result;
+}
+
+
+
MessageComposer.prototype.checkAutocomplete = function () {
- var textarea = this.contentEditableEl ? this.contentEditableEl[0] : this.textareaEl;
- var pos = getFieldSelection(textarea);
- var value = getFieldValue(textarea).substr(0, pos);
+ var pos, value;
+ if (this.richTextareaEl) {
+ var textarea = this.richTextareaEl[0];
+ var valueCaret = getRichValueWithCaret(textarea);
+ var value = valueCaret[0];
+ var pos = valueCaret[1] >= 0 ? valueCaret[1] : value.length;
+ } else {
+ var textarea = this.textareaEl[0];
+ var pos = getFieldSelection(textarea);
+ var value = textarea.value;
+ }
- console.log(pos, value);
+ value = value.substr(0, pos);
var matches = value.match(/:([A-Za-z_0-z\+-]*)$/);
- if (matches) {
+ if (matches/* && !this.richTextareaEl*/) {
if (this.previousQuery == matches[0]) {
return;
}
@@ -509,31 +624,157 @@ MessageComposer.prototype.onFocusBlur = function (e) {
} else {
setTimeout(this.checkAutocomplete.bind(this), 100);
}
+ if (this.richTextareaEl) {
+ document.execCommand('enableObjectResizing', !this.isActive, !this.isActive);
+ }
}
-MessageComposer.prototype.onEmojiSelected = function (code) {
- console.log('emoji selected', code);
+MessageComposer.prototype.onRichPaste = function (e) {
+ var cData = (e.originalEvent || e).clipboardData,
+ items = cData && cData.items || [],
+ i;
+ for (i = 0; i < items.length; i++) {
+ if (items[i].kind == 'file') {
+ e.preventDefault();
+ return true;
+ }
+ }
- var emoji = EmojiHelper.emojis[code];
+ var text = (e.originalEvent || e).clipboardData.getData('text/plain');
+ setZeroTimeout(this.onChange.bind(this), 0);
+ if (text.length) {
+ document.execCommand('insertText', false, text);
+ return cancelEvent(e);
+ }
+ return true;
+}
- var textarea = this.textareaEl[0];
- var fullValue = textarea.value;
- var pos = this.isActive ? getFieldSelection(textarea) : fullValue.length;
- var suffix = fullValue.substr(pos);
- var prefix = fullValue.substr(0, pos);
- var matches = prefix.match(/:([A-Za-z_0-z\+-]*)$/);
+MessageComposer.prototype.onRichPasteNode = function (e) {
+ var element = (e.originalEvent || e).target,
+ src = (element || {}).src || '',
+ remove = false;
+
+ if (src.substr(0, 5) == 'data:') {
+ remove = true;
+ var blob = dataUrlToBlob(src);
+ this.onFilePaste(blob);
+ setZeroTimeout(function () {
+ element.parentNode.removeChild(element);
+ })
+ }
+ else if (src && !src.match(/img\/blank\.gif/)) {
+ var replacementNode = document.createTextNode(' ' + src + ' ');
+ setTimeout(function () {
+ element.parentNode.replaceChild(replacementNode, element);
+ }, 100);
+ }
+}
- if (matches && matches[0]) {
- var newValue = prefix.substr(0, matches.index) + ':' + emoji[1] + ': ' + suffix;
- var newPos = matches.index + emoji[1].length + 3;
- } else {
- var newValue = prefix + ':' + emoji[1] + ': ' + suffix;
- var newPos = prefix.length + emoji[1].length + 3;
+
+
+MessageComposer.prototype.onEmojiSelected = function (code, autocomplete) {
+ if (this.richTextareaEl) {
+ var textarea = this.richTextareaEl[0];
+ if (!this.isActive) {
+ if (!this.restoreSelection()) {
+ setRichFocus(textarea);
+ }
+ }
+ if (autocomplete) {
+ var valueCaret = getRichValueWithCaret(textarea);
+ var fullValue = valueCaret[0];
+ var pos = valueCaret[1] >= 0 ? valueCaret[1] : fullValue.length;
+ var suffix = fullValue.substr(pos);
+ var prefix = fullValue.substr(0, pos);
+ var matches = prefix.match(/:([A-Za-z0-9\-\+\*_]*)$/);
+ var emoji = EmojiHelper.emojis[code];
+
+ var newValuePrefix;
+ if (matches && matches[0]) {
+ newValuePrefix = prefix.substr(0, matches.index) + ':' + emoji[1] + ':';
+ } else {
+ newValuePrefix = prefix + ':' + emoji[1] + ':';
+ }
+ textarea.value = newValue;
+
+ this.selId = (this.selId || 0) + 1;
+ var html = this.getRichHtml(newValuePrefix) + '
' + this.getRichHtml(suffix);
+
+ this.richTextareaEl.html(html);
+ setRichFocus(textarea, $('#composer_sel' + this.selId)[0]);
+ } else {
+ document.execCommand('insertHTML', false, this.getEmojiHtml(code));
+ }
+ }
+ else {
+ var textarea = this.textareaEl[0];
+ var fullValue = textarea.value;
+ var pos = this.isActive ? getFieldSelection(textarea) : fullValue.length;
+ var suffix = fullValue.substr(pos);
+ var prefix = fullValue.substr(0, pos);
+ var matches = autocomplete && prefix.match(/:([A-Za-z0-9\-\+\*_]*)$/);
+ var emoji = EmojiHelper.emojis[code];
+
+ if (matches && matches[0]) {
+ var newValue = prefix.substr(0, matches.index) + ':' + emoji[1] + ': ' + suffix;
+ var newPos = matches.index + emoji[1].length + 3;
+ } else {
+ var newValue = prefix + ':' + emoji[1] + ': ' + suffix;
+ var newPos = prefix.length + emoji[1].length + 3;
+ }
+ textarea.value = newValue;
+ setFieldSelection(textarea, newPos);
}
- textarea.value = newValue;
- setFieldSelection(textarea, newPos);
this.hideSuggestions();
+ this.onChange();
+}
+
+MessageComposer.prototype.onChange = function (e) {
+ if (this.richTextareaEl) {
+ delete this.keyupStarted;
+ this.textareaEl.val(getRichValue(this.richTextareaEl[0])).trigger('change');
+ }
+}
+
+MessageComposer.prototype.getEmojiHtml = function (code, emoji) {
+ emoji = emoji || EmojiHelper.emojis[code];
+ var iconSize = 20;
+ var spritesheet = EmojiHelper.spritesheetPositions[code];
+ var categoryIndex = spritesheet[0];
+ var pos = spritesheet[1];
+ var x = iconSize * spritesheet[3];
+ var y = iconSize * spritesheet[2];
+
+ return '
![:' + encodeEntities(emoji[1]) + ':](img/blank.gif)
';
+}
+
+MessageComposer.prototype.setValue = function (text) {
+ if (this.richTextareaEl) {
+ this.richTextareaEl.html(this.getRichHtml(text));
+ this.lastLength = text.length;
+ } else {
+ this.textareaEl.val(text);
+ }
+}
+
+MessageComposer.prototype.getRichHtml = function (text) {
+ return $('
').text(text).html().replace(/:([A-Za-z0-9\-\+\*_]+?):/gi, (function (all, shortcut) {
+ var code = EmojiHelper.shortcuts[shortcut];
+ if (code !== undefined) {
+ return this.getEmojiHtml(code);
+ }
+ return all;
+ }).bind(this));
+}
+
+
+MessageComposer.prototype.focus = function () {
+ if (this.richTextareaEl) {
+ setRichFocus(this.richTextareaEl[0]);
+ } else {
+ setFieldSelection(this.textareaEl[0]);
+ }
}
@@ -567,7 +808,7 @@ MessageComposer.prototype.showEmojiSuggestions = function (codes) {
}
MessageComposer.prototype.updatePosition = function () {
- var offset = this.textareaEl.offset();
+ var offset = (this.richTextareaEl || this.textareaEl).offset();
var height = this.autoCompleteEl.outerHeight();
this.autoCompleteEl.css({top: offset.top - height, left: offset.left});
}
@@ -576,3 +817,9 @@ MessageComposer.prototype.hideSuggestions = function () {
this.autoCompleteEl.hide();
delete this.autocompleteShown;
}
+
+MessageComposer.prototype.resetTyping = function () {
+ this.lastTyping = 0;
+ this.lastLength = 0;
+}
+
diff --git a/app/partials/desktop/im.html b/app/partials/desktop/im.html
index c152e5fc..bb632a7f 100644
--- a/app/partials/desktop/im.html
+++ b/app/partials/desktop/im.html
@@ -171,13 +171,13 @@
-
diff --git a/app/partials/mobile/im.html b/app/partials/mobile/im.html
index 5bd132b6..a67e8749 100644
--- a/app/partials/mobile/im.html
+++ b/app/partials/mobile/im.html
@@ -138,9 +138,7 @@
-
-
-
+
diff --git a/app/webogram.appcache b/app/webogram.appcache
index 9802e602..83172322 100644
--- a/app/webogram.appcache
+++ b/app/webogram.appcache
@@ -1,6 +1,6 @@
CACHE MANIFEST
-# 57
+# 59
NETWORK:
*