diff --git a/app/css/app.css b/app/css/app.css index b7a22ed3..a161fdad 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -2103,6 +2103,7 @@ a.composer_emoji_btn:hover { width: 18px; background-repeat: no-repeat; text-indent: -9999px; + border: 0 none; } /* widths and heights calculated according to spritesheet dimensions and icon size */ @@ -2128,19 +2129,47 @@ a.composer_emoji_btn:hover { } -.composer_emoji_btn .emoji { +.emoji-w20 { width: 20px; height: 20px; vertical-align: middle; - border: 0 none; display: inline-block; } -.composer_emoji_btn .emoji-spritesheet-0 {background-size: 540px 140px;} -.composer_emoji_btn .emoji-spritesheet-1 {background-size: 580px 80px;} -.composer_emoji_btn .emoji-spritesheet-2 {background-size: 660px 140px;} -.composer_emoji_btn .emoji-spritesheet-3 {background-size: 680px 60px;} -.composer_emoji_btn .emoji-spritesheet-4 {background-size: 680px 140px;} +.emoji-w20.emoji-spritesheet-0 {background-size: 540px 140px;} +.emoji-w20.emoji-spritesheet-1 {background-size: 580px 80px;} +.emoji-w20.emoji-spritesheet-2 {background-size: 660px 140px;} +.emoji-w20.emoji-spritesheet-3 {background-size: 680px 60px;} +.emoji-w20.emoji-spritesheet-4 {background-size: 680px 140px;} +.composer_dropdown { + display: none; + /*max-width: 100%;*/ + border-radius: 2px; + padding: 6px 0; + border: 1px solid rgba(15, 60, 96, 0.2); + -webkit-box-shadow: 0px 1px 3px 0px rgba(60,75,87,0.27); + -moz-box-shadow: 0px 1px 3px 0px rgba(60,75,87,0.27); + box-shadow: 0px 1px 3px 0px rgba(60,75,87,0.27); +} + +.composer_dropdown > li > a { + display: block; + font-size: 13px; + line-height: 15px; + padding: 4px 10px; + color: #52719a; +} +.composer_dropdown li a:hover, +.composer_dropdown li a.composer_emoji_option_active { + color: #52719a; + background: #f2f6fa; +} +.composer_emoji_shortcut { + display: inline-block; + vertical-align: top; + margin-left: 15px; + line-height: 20px; +} diff --git a/app/js/directives.js b/app/js/directives.js index 0c6f78d9..632f658d 100755 --- a/app/js/directives.js +++ b/app/js/directives.js @@ -1073,10 +1073,22 @@ angular.module('myApp.directives', ['myApp.filters']) function link ($scope, element, attrs) { var emojiButton = $('.composer_emoji_insert_btn', element)[0]; - new EmojiTooltip(emojiButton); + new EmojiTooltip(emojiButton, { + onEmojiSelected: function (code) { + composer.onEmojiSelected(code); + } + }); var emojiPanel = $('.composer_emoji_panel', element)[0]; - new EmojiPanel(emojiPanel); + new EmojiPanel(emojiPanel, { + onEmojiSelected: function (code) { + composer.onEmojiSelected(code); + } + }); + + + var messageField = $('textarea', element)[0]; + var composer = new MessageComposer(messageField, {}); return; diff --git a/app/js/message_composer.js b/app/js/message_composer.js index 1a5d21cd..ece48b22 100644 --- a/app/js/message_composer.js +++ b/app/js/message_composer.js @@ -104,7 +104,15 @@ function searchEmojis (q) { indexEmojis(); - return SearchIndexManager.search(q, index); + var foundObject = SearchIndexManager.search(q, index); + var foundCodes = []; + var code; + for (code in foundObject) { + if (foundObject.hasOwnProperty(code)) { + foundCodes.push(code); + } + } + return foundCodes; } global.EmojiHelper = { @@ -244,7 +252,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('')); } @@ -262,7 +270,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('')); @@ -291,7 +299,7 @@ EmojiTooltip.prototype.hide = function () { function EmojiPanel (containerEl, options) { options = options || {}; - // var self = this; + var self = this; this.containerEl = $(containerEl); this.onEmojiSelected = options.onEmojiSelected; @@ -332,7 +340,7 @@ EmojiPanel.prototype.update = function () { pos = spritesheet[1]; x = iconSize * spritesheet[3]; y = iconSize * spritesheet[2]; - html.push(''); + html.push(''); } } self.containerEl.html(html.join('')); @@ -349,6 +357,24 @@ function MessageComposer (textarea, options) { this.textareaEl.on('keyup keydown', this.onKeyEvent.bind(this)); this.textareaEl.on('focus blur', this.onFocusBlur.bind(this)); + this.autoCompleteEl = $('
').appendTo(document.body); + + var self = this; + this.autoCompleteEl.on('mousedown', function (e) { + e = e.originalEvent || e; + var target = $(e.target), code; + if (target.hasClass('emoji') || target.hasClass('composer_emoji_shortcut')) { + target = $(target[0].parentNode); + } + if (code = target.attr('data-code')) { + if (self.onEmojiSelected) { + self.onEmojiSelected(code); + } + EmojiHelper.pushPopularEmoji(code); + } + return cancelEvent(e); + }); + this.isActive = false; } @@ -357,24 +383,68 @@ MessageComposer.prototype.onKeyEvent = function (e) { 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); + } + } + + 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); + } + + 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); + } + return cancelEvent(e); + } + } } -MessageComposer.prototype.checkAutocomplete = function (e) { - var pos = getFieldSelection(e.target); +MessageComposer.prototype.checkAutocomplete = function () { + var textarea = this.textareaEl[0]; + var pos = getFieldSelection(textarea); var value = this.textareaEl[0].value.substr(0, pos); - var matches = value.match(/:([A-Za-z_]*)$/); + var matches = value.match(/:([A-Za-z_0-z\+-]*)$/); if (matches) { - if (matches[1]) { - var found = EmojiHelper.searchEmojis(matches[1]); - self.showEmojiSuggestions(found); + if (this.previousQuery == matches[0]) { + return; + } + this.previousQuery = matches[0]; + var query = SearchIndexManager.cleanSearchText(matches[1]); + if (query.length) { + var found = EmojiHelper.searchEmojis(query); + if (found.length) { + this.showEmojiSuggestions(found); + } else { + this.hideSuggestions(); + } } else { - EmojiHelper.getPopularEmoji(function (found) { - self.showEmojiSuggestions(found); - }); + EmojiHelper.getPopularEmoji((function (found) { + this.showEmojiSuggestions(found); + }).bind(this)); } } else { - self.hideSuggestions(); + delete this.previousQuery; + this.hideSuggestions(); } } @@ -383,14 +453,73 @@ MessageComposer.prototype.onFocusBlur = function (e) { if (!this.isActive) { this.hideSuggestions(); + } else { + setTimeout(this.checkAutocomplete.bind(this), 100); } } +MessageComposer.prototype.onEmojiSelected = function (code) { + console.log('emoji selected', code); + + var emoji = EmojiHelper.emojis[code]; + + 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\+-]*)$/); + + 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); + + this.hideSuggestions(); +} + MessageComposer.prototype.showEmojiSuggestions = function (codes) { + var html = []; + var iconSize = Config.Mobile ? 26 : 20; + + var emoticonCode, emoticonData, spritesheet, pos, categoryIndex; + var count = Math.min(5, codes.length); + var i, x, y; + + for (i = 0; i < count; i++) { + emoticonCode = codes[i]; + if (emoticonCode.code) { + emoticonCode = emoticonCode.code; + } + if (emoticonData = Config.Emoji[emoticonCode]) { + spritesheet = EmojiHelper.spritesheetPositions[emoticonCode]; + categoryIndex = spritesheet[0]; + pos = spritesheet[1]; + x = iconSize * spritesheet[3]; + y = iconSize * spritesheet[2]; + html.push('