/*! * Webogram v0.5.3 - messaging web application for MTProto * https://github.com/zhukov/webogram * Copyright (C) 2014 Igor Zhukov * https://github.com/zhukov/webogram/blob/master/LICENSE */ 'use strict'; /* EmojiHelper */ (function (global, emojis, categories, spritesheets) { var emojis = {}; var shortcuts = {}; var spritesheetPositions = {}; var index = false; var popular = 'joy,kissing_heart,heart,heart_eyes,blush,grin,+1,relaxed,pensive,smile,sob,kiss,unamused,flushed,stuck_out_tongue_winking_eye,see_no_evil,wink,smiley,cry,stuck_out_tongue_closed_eyes,scream,rage,smirk,disappointed,sweat_smile,kissing_closed_eyes,speak_no_evil,relieved,grinning,yum,laughing,ok_hand,neutral_face,confused'.split(','); var i, j, code, shortcut, emoji, row, column, totalColumns; var len1, len2; for (i = 0, len1 = categories.length; i < len1; i++) { totalColumns = spritesheets[i][1]; for (j = 0, len2 = categories[i].length; j < len2; j++) { code = categories[i][j]; emoji = Config.Emoji[code]; shortcut = emoji[1][0]; emojis[code] = [emoji[0], shortcut]; shortcuts[shortcut] = code; spritesheetPositions[code] = [i, j, Math.floor(j / totalColumns), j % totalColumns]; } } function getPopularEmoji (callback) { ConfigStorage.get('emojis_popular', function (popEmojis) { var result = []; if (popEmojis && popEmojis.length) { for (var i = 0, len = popEmojis.length; i < len; i++) { result.push({code: popEmojis[i][0], rate: popEmojis[i][1]}); } callback(result); return; }; ConfigStorage.get('emojis_recent', function (recentEmojis) { recentEmojis = recentEmojis || popular || []; var shortcut, code; for (var i = 0, len = recentEmojis.length; i < len; i++) { shortcut = recentEmojis[i]; if (Array.isArray(shortcut)) { shortcut = shortcut[0]; } if (shortcut && typeof shortcut === 'string') { if (shortcut.charAt(0) == ':') { shortcut = shortcut.substr(1, shortcut.length - 2); } if (code = shortcuts[shortcut]) { result.push({code: code, rate: 1}); } } } callback(result); }); }); } function pushPopularEmoji (code) { getPopularEmoji(function (popularEmoji) { var exists = false; var count = popularEmoji.length; var result = []; for (var i = 0; i < count; i++) { if (popularEmoji[i].code == code) { exists = true; popularEmoji[i].rate++; } result.push([popularEmoji[i].code, popularEmoji[i].rate]); } if (exists) { result.sort(function (a, b) { return b[1] - a[1]; }); } else { if (result.length > 41) { result = result.slice(0, 41); } result.push([code, 1]); } ConfigStorage.set({emojis_popular: result}); }); } function indexEmojis () { if (index === false) { index = SearchIndexManager.createIndex(); var shortcut; for (shortcut in shortcuts) { if (shortcuts.hasOwnProperty(shortcut)) { SearchIndexManager.indexObject(shortcuts[shortcut], shortcut, index); } } } } function searchEmojis (q) { indexEmojis(); 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 = { emojis: emojis, shortcuts: shortcuts, spritesheetPositions: spritesheetPositions, getPopularEmoji: getPopularEmoji, pushPopularEmoji: pushPopularEmoji, indexEmojis: indexEmojis, searchEmojis: searchEmojis }; })(window, Config.Emoji, Config.EmojiCategories, Config.EmojiCategorySpritesheetDimens); function EmojiTooltip (btnEl, options) { options = options || {}; var self = this; this.btnEl = $(btnEl); this.onEmojiSelected = options.onEmojiSelected; this.onStickerSelected = options.onStickerSelected; this.getStickers = options.getStickers; this.getStickerImage = options.getStickerImage; this.onStickersetSelected = options.onStickersetSelected; this.langpack = options.langpack || {}; if (!Config.Navigator.touch) { $(this.btnEl).on('mouseenter mouseleave', function (e) { self.isOverBtn = e.type == 'mouseenter'; self.createTooltip(); if (self.isOverBtn) { self.onMouseEnter(true); } else { self.onMouseLeave(true); } }); } $(this.btnEl).on('mousedown', function (e) { if (!self.shown) { clearTimeout(self.showTimeout); delete self.showTimeout; self.createTooltip(); self.show(); } else { clearTimeout(self.hideTimeout); delete self.hideTimeout; self.hide(); } return cancelEvent(e); }); $(document).on('mousedown', function (e) { if (self.shown) { self.hide(); } }); } EmojiTooltip.prototype.onMouseEnter = function (triggerShow) { if (this.hideTimeout) { clearTimeout(this.hideTimeout); delete this.hideTimeout; } else if (triggerShow && !this.showTimeout) { this.showTimeout = setTimeout(this.show.bind(this), 100); } }; EmojiTooltip.prototype.onMouseLeave = function (triggerUnshow) { if (!this.hideTimeout) { var self = this; this.hideTimeout = setTimeout(function () { self.hide(); }, 600); } else if (triggerUnshow && this.showTimeout) { clearTimeout(this.showTimeout); delete this.showTimeout; } }; EmojiTooltip.prototype.createTooltip = function () { if (this.tooltipEl) { return false; } var html = '
\
\
' +this.langpack.im_emoji_tab + '
\
' +this.langpack.im_stickers_tab + '
\
\
\
\
\
\
\
\
\
\ \ \ \ \ \ \
\
\
\
\
\
\
\
\
\
\
\
'; html = html.replace(/>\s+<'); var self = this; this.tooltipEl = $(html).appendTo(document.body); this.tabsEl = $('.composer_emoji_tooltip_tabs', this.tooltipEl); this.categoriesEl = $('.composer_emoji_tooltip_categories', this.tooltipEl); this.stickersCategoriesEl = $('.composer_emoji_tooltip_tab_stickers_content .composer_emoji_tooltip_categories', this.tooltipEl); this.contentEl = $('.composer_emoji_tooltip_content', this.tooltipEl); this.emojiContentEl = $('.composer_emoji_tooltip_content_emoji', this.tooltipEl); this.stickersContentEl = $('.composer_emoji_tooltip_content_stickers', this.tooltipEl); // Tabs angular.forEach(['emoji', 'stickers'], function (tabName, tabIndex) { var tab = $('.composer_emoji_tooltip_tab_' + tabName, self.tabsEl) .on('mousedown', function (e) { self.selectTab(tabIndex); return cancelEvent(e); }); if (!Config.Navigator.touch) { tab.on('mouseenter mouseleave', function (e) { clearTimeout(self.selectTabTimeout); if (e.type == 'mouseenter') { self.selectTabTimeout = setTimeout(function () { self.selectTab(tabIndex); }, 300); } }); } }); // Categories var handleEvents = 'mousedown'; if (!Config.Navigator.touch) { handleEvents += ' mouseover mouseout'; } this.categoriesEl.on(handleEvents, function (e) { e = e.originalEvent || e; var target = e.target; if (target.tagName != 'A') { target = target.parentNode; } if (target.tagName != 'A') { return; } var catIndex = parseInt(target.getAttribute('data-category')); if (e.type == 'mousedown') { self.selectCategory(catIndex); return cancelEvent(e); } if (self.tab) { return; } var isOver = e.type == 'mouseover'; if (isOver && self.selectCategoryIndex == catIndex) { return; } clearTimeout(self.selectCategoryTimeout); delete self.selectCategoryTimeout; if (isOver) { self.selectCategoryIndex = catIndex; self.selectCategoryTimeout = setTimeout(function () { delete self.selectCategoryIndex; delete self.selectCategoryTimeout; self.selectCategory(catIndex); }, 300); } else { delete self.selectCategoryIndex; } }); this.emojiScroller = new Scroller(this.emojiContentEl, {classPrefix: 'composer_emoji_tooltip'}); this.stickersScroller = new Scroller(this.stickersContentEl, {classPrefix: 'composer_emoji_tooltip'}); this.stickersScroller.onScroll(function (el, st) { self.onStickersScroll(el, st); }); this.contentEl.on('mousedown', function (e) { e = e.originalEvent || e; var target = $(e.target), code, sticker, stickerset; if (target[0].tagName != 'A') { target = $(target[0].parentNode); } if (code = target.attr('data-code')) { if (self.onEmojiSelected) { self.onEmojiSelected(code); } EmojiHelper.pushPopularEmoji(code); } if (sticker = target.attr('data-sticker')) { if (self.onStickerSelected) { self.onStickerSelected(sticker); } if (Config.Mobile) { self.hide(); } } if (stickerset = target.attr('data-stickerset')) { if (self.onStickersetSelected) { self.onStickersetSelected(stickerset); } self.hide(); } return cancelEvent(e); }); if (!Config.Navigator.touch) { this.tooltipEl.on('mouseenter mouseleave', function (e) { if (e.type == 'mouseenter') { self.onMouseEnter(); } else { self.onMouseLeave(); } }); } this.selectTab(0); $(window).on('resize', this.updatePosition.bind(this)); return true; } EmojiTooltip.prototype.selectCategory = function (cat, force) { if (!this.tab && this.cat === cat && !force) { return false; } $('.active', this.categoriesEl).removeClass('active'); this.cat = cat; if (this.tab) { this.activateStickerCategory(); this.updateStickersContents(force); } else { $(this.categoriesEl[this.tab].childNodes[cat]).addClass('active'); this.updateEmojiContents(); } }; EmojiTooltip.prototype.selectTab = function (tab, force) { if (this.tab === tab && !force) { return false; } this.tab = tab; this.selectCategory(0, true); var self = this; setTimeout(function () { $(self.tooltipEl).toggleClass('composer_emoji_tooltip_tabs_stickers_active', tab == 1); }, 0); }; EmojiTooltip.prototype.updateEmojiContents = function () { var html = []; var self = this; var iconSize = 26; var renderContent = function () { self.emojiContentEl.html(html.join('')); self.emojiScroller.reinit(); } if (this.cat > 0) { var categoryIndex = this.cat - 1; var emoticonCodes = Config.EmojiCategories[categoryIndex]; var totalColumns = Config.EmojiCategorySpritesheetDimens[categoryIndex][1]; var count = emoticonCodes.length; var emoticonCode, emoticonData, i, x, y; for (i = 0; i < count; i++) { emoticonCode = emoticonCodes[i]; emoticonData = Config.Emoji[emoticonCode]; x = iconSize * (i % totalColumns); y = iconSize * Math.floor(i / totalColumns); html.push(''); } renderContent(); } else { EmojiHelper.getPopularEmoji(function (popularEmoji) { var emoticonCode, emoticonData, spritesheet, pos, categoryIndex; var count = popularEmoji.length; var i, x, y; for (i = 0; i < count; i++) { emoticonCode = popularEmoji[i].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(''); } } renderContent(); }); } }; EmojiTooltip.prototype.updateStickersContents = function (force) { var html = []; var categoriesHtml = []; var self = this; var iconSize = 26; var scrollStickers = function () { var scrollTop = self.cat ? self.stickersetPositions[self.cat][0] : 0; self.stickersScroller.scrollTo(scrollTop, force ? 0 : 200); } if (!force && self.stickersetPositions.length) { scrollStickers(); return; } var renderStickers = function (stickersets) { var set, docID, i, j, len1, len2; for (i = 0, len1 = stickersets.length; i < len1; i++) { set = stickersets[i]; if (!set.docIDs.length) { continue; } html.push('
'); if (set.title) { html.push( '', encodeEntities(set.title), '' ); } if (!set.id) { categoriesHtml.push(''); } else { categoriesHtml.push(''); } for (j = 0, len2 = set.docIDs.length; j < len2; j++) { docID = set.docIDs[j]; html.push(''); } html.push('
'); } self.stickersContentEl.html(html.join('')); self.stickersCategoriesEl.html(categoriesHtml.join('')); self.stickersScroller.reinit(); var scrollPositions = []; $('.composer_stickerset_wrap', self.stickersContentEl).each(function (k, stickerSetEl) { var height = stickerSetEl.offsetHeight; var top = stickerSetEl.offsetTop; scrollPositions.push([top, height]); }); self.stickersetPositions = scrollPositions; scrollStickers(); var preload = []; self.contentEl.find('.composer_sticker_btn').each(function (k, element) { if (k < 12) { self.replaceStickerImage(element); } else { preload.push([element.offsetTop, element]); } }); self.stickersPreload = preload; self.stickersCategoriesEl.find('.composer_sticker_btn').each(function (k, element) { self.replaceStickerImage(element); }); }; this.getStickers(renderStickers); }; EmojiTooltip.prototype.replaceStickerImage = function (element) { element = $(element); this.getStickerImage(element, element.attr('data-sticker')); } EmojiTooltip.prototype.onStickersScroll = function (scrollable, scrollTop) { var ch = scrollable.clientHeight; var sh = scrollable.scrollHeight; var len = this.stickersetPositions.length; var currentCat = false; var currentPos, i; if (scrollTop < 20) { currentCat = 0; } else if (scrollTop > sh - ch - 20) { currentCat = len - 1; } else { for (i = 0; i < len; i++) { currentPos = this.stickersetPositions[i]; if (scrollTop >= currentPos[0] && scrollTop < (currentPos[0] + currentPos[1])) { currentCat = i; break; } } } var len = this.stickersPreload.length; if (len) { for (i = 0; i < len; i++) { currentPos = this.stickersPreload[i]; if (currentPos[0] >= scrollTop && currentPos[0] <= scrollTop + ch) { // console.log('replace', currentPos[1], i); this.replaceStickerImage(currentPos[1]); this.stickersPreload.splice(i, 1); i--; len--; } } } // console.log('on sticker scroll', scrollTop, ch, sh, currentCat, this.stickersetPositions); if (this.cat === currentCat || currentCat === false) { return; } $('.active', this.categoriesEl).removeClass('active'); this.cat = currentCat; this.activateStickerCategory(); }; EmojiTooltip.prototype.onStickersChanged = function () { if (this.tab) { this.updateStickersContents(true); } }; EmojiTooltip.prototype.activateStickerCategory = function () { var categoriesEl = this.categoriesEl[1]; var categoryEl = categoriesEl.childNodes[this.cat]; if (!categoryEl) { return; } $(categoryEl).addClass('active'); var left = categoryEl.offsetLeft; var width = categoryEl.offsetWidth; var viewportWidth = categoriesEl.clientWidth; // console.log('current cat el', categoryEl, left, width, viewportWidth); $(categoriesEl).animate({scrollLeft: left - (viewportWidth - width) / 2}, 200); } EmojiTooltip.prototype.updatePosition = function () { var offset = this.btnEl.offset(); this.tooltipEl.css({top: offset.top, left: offset.left}); }; EmojiTooltip.prototype.show = function () { this.updatePosition(); if (this.tab) { this.updateStickersContents(true); } else { this.updateEmojiContents(); } 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 () { if (this.tooltipEl) { this.tooltipEl.removeClass('composer_emoji_tooltip_shown'); this.btnEl.removeClass('composer_emoji_insert_btn_on'); } delete this.hideTimeout; delete this.shown; }; function EmojiPanel (containerEl, options) { options = options || {}; var self = this; this.containerEl = $(containerEl); this.onEmojiSelected = options.onEmojiSelected; this.containerEl.on('mousedown', function (e) { e = e.originalEvent || e; var target = $(e.target), code; if (target[0].tagName != 'A') { target = $(target[0].parentNode); } if (code = target.attr('data-code')) { if (self.onEmojiSelected) { self.onEmojiSelected(code); } EmojiHelper.pushPopularEmoji(code); } return cancelEvent(e); }); this.update(); } EmojiPanel.prototype.update = function () { var html = []; var self = this; var iconSize = Config.Mobile ? 26 : 20; EmojiHelper.getPopularEmoji(function (popularEmoji) { var emoticonCode, emoticonData, spritesheet, pos, categoryIndex; var count = popularEmoji.length; var i, x, y; for (i = 0; i < count; i++) { emoticonCode = popularEmoji[i].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(''); } } self.containerEl.html(html.join('')); }); } function MessageComposer (textarea, options) { var self = this; this.textareaEl = $(textarea); this.setUpInput(); this.autoCompleteWrapEl = $('
').appendTo(document.body); var autoCompleteEl = $('
').appendTo(this.autoCompleteWrapEl); options.dropdownDirective(autoCompleteEl, function (scope, newAutoCompleteEl) { self.autoCompleteEl = newAutoCompleteEl; self.autoCompleteScope = scope; self.setUpAutoComplete(); }); this.isActive = false; this.onTyping = options.onTyping; this.onMessageSubmit = options.onMessageSubmit; this.getSendOnEnter = options.getSendOnEnter; this.onFilePaste = options.onFilePaste; this.onCommandSend = options.onCommandSend; this.onInlineResultSend = options.onInlineResultSend; this.mentions = options.mentions; this.commands = options.commands; } MessageComposer.autoCompleteRegEx = /(\s|^)(:|@|\/)([A-Za-z0-9\-\+\*@_]*)$/; MessageComposer.prototype.setUpInput = function () { this.inlinePlaceholderWrap = $('
').prependTo(this.textareaEl[0].parentNode); this.inlinePlaceholderPrefixEl = $('').appendTo(this.inlinePlaceholderWrap); this.inlinePlaceholderEl = $('').appendTo(this.inlinePlaceholderWrap); if ('contentEditable' in document.body) { this.setUpRich(); } else { this.setUpPlaintext(); } if (!Config.Mobile) { var sbWidth = getScrollWidth(); if (sbWidth) { (this.richTextareaEl || this.textareaEl).css({marginRight: -sbWidth}); } } } MessageComposer.prototype.setInlinePlaceholder = function (prefix, placeholder) { this.inlinePlaceholderPrefix = prefix this.inlinePlaceholderPrefixEl.html(encodeEntities(prefix)); this.inlinePlaceholderEl.html(encodeEntities(placeholder)); this.onChange(); } MessageComposer.prototype.updateInlinePlaceholder = function () { var prefix = this.inlinePlaceholderPrefix; if (prefix) { var value = this.textareaEl.val(); this.inlinePlaceholderWrap.toggleClass('active', value == prefix); } } MessageComposer.prototype.setUpAutoComplete = function () { this.scroller = new Scroller(this.autoCompleteEl, {maxHeight: 180}); var self = this; this.autoCompleteEl.on('mousedown', function (e) { e = e.originalEvent || e; var target = $(e.target), mention, code, command, inlineID; if (target[0].tagName != 'A') { target = $(target[0].parentNode); } if (code = target.attr('data-code')) { if (self.onEmojiSelected) { self.onEmojiSelected(code, true); } EmojiHelper.pushPopularEmoji(code); } if (mention = target.attr('data-mention')) { self.onMentionSelected(mention); } if (command = target.attr('data-command')) { if (self.onCommandSelected) { self.onCommandSelected(command); } self.hideSuggestions(); } if (inlineID = target.attr('data-inlineid')) { if (self.onInlineResultSend) { self.onInlineResultSend(inlineID); } self.hideSuggestions(); } return cancelEvent(e); }); } MessageComposer.prototype.setUpRich = function () { this.textareaEl.hide(); this.richTextareaEl = $('
'); this.textareaEl[0].parentNode.insertBefore(this.richTextareaEl[0], this.textareaEl[0]); 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 () { this.textareaEl.on('keyup keydown', this.onKeyEvent.bind(this)); this.textareaEl.on('focus blur', this.onFocusBlur.bind(this)); } MessageComposer.prototype.onKeyEvent = function (e) { var self = this; if (e.type == 'keyup') { // console.log(dT(), 'keyup', e.keyCode); this.checkAutocomplete(); var length = false; if (this.richTextareaEl) { clearTimeout(this.updateValueTO); var now = tsNow(); if (this.keyupStarted === undefined) { this.keyupStarted = now; } if (now - this.keyupStarted > 3000 || true) { this.onChange(); } else { length = this.richTextareaEl[0].textContent.length; if (this.wasEmpty != !length) { this.wasEmpty = !this.wasEmpty; this.onChange(); } else if (this.inlinePlaceholderPrefix) { this.onChange(); } else { this.updateValueTO = setTimeout(this.onChange.bind(this), 1000); } } } if (this.onTyping) { var now = tsNow(); if (now - this.lastTyping > 5000) { if (length === false) { 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 (e.type == 'keydown') { var checkSubmit = !this.autocompleteShown; if (this.autocompleteShown) { if (e.keyCode == 38 || e.keyCode == 40) { // UP / DOWN var next = e.keyCode == 40; var currentSel = $(this.autoCompleteEl).find('li.composer_autocomplete_option_active'); var allLIs = Array.prototype.slice.call($(this.autoCompleteEl).find('li')); var nextSel; if (currentSel.length) { var pos = allLIs.indexOf(currentSel[0]); var nextPos = pos + (next ? 1 : -1); nextSel = allLIs[nextPos]; currentSel.removeClass('composer_autocomplete_option_active'); if (nextSel) { $(nextSel).addClass('composer_autocomplete_option_active'); this.scroller.scrollToNode(nextSel); // console.log(dT(), 'keydown cancel', e.keyCode); return cancelEvent(e); } } nextSel = allLIs[next ? 0 : allLIs.length - 1]; this.scroller.scrollToNode(nextSel); $(nextSel).addClass('composer_autocomplete_option_active'); // console.log(dT(), 'keydown cancel', e.keyCode); return cancelEvent(e); } if (e.keyCode == 13 || e.keyCode == 9) { // Enter or Tab var currentSel = $(this.autoCompleteEl).find('li.composer_autocomplete_option_active'); if (!currentSel.length && e.keyCode == 9) { currentSel = $(this.autoCompleteEl).find('li:first'); } currentSel = currentSel.find('a:first'); var code, mention, command, inlineID; if (code = currentSel.attr('data-code')) { this.onEmojiSelected(code, true); EmojiHelper.pushPopularEmoji(code); return cancelEvent(e); } if (mention = currentSel.attr('data-mention')) { this.onMentionSelected(mention); return cancelEvent(e); } if (command = currentSel.attr('data-command')) { if (this.onCommandSelected) { this.onCommandSelected(command, e.keyCode == 9); } return cancelEvent(e); } if (inlineID = currentSel.attr('data-inlineid')) { if (self.onInlineResultSend) { self.onInlineResultSend(inlineID); } self.hideSuggestions(); // console.log(dT(), 'keydown cancel', e.keyCode); return cancelEvent(e); } checkSubmit = true; } } if (checkSubmit && 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); } } } } 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 (forceFull) { 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; if (!pos) { this.cleanRichTextarea(value, true); } } else { var textarea = this.textareaEl[0]; var pos = getFieldSelection(textarea); var value = textarea.value; } if (value && this.curInlineResults && this.curInlineResults.text == value) { console.trace(dT(), value, this.curInlineResults); this.showInlineSuggestions(this.curInlineResults); return; }; if (!forceFull) { value = value.substr(0, pos); } var matches = value.match(MessageComposer.autoCompleteRegEx); if (matches) { if (this.previousQuery == matches[0]) { return; } this.previousQuery = matches[0]; var query = SearchIndexManager.cleanSearchText(matches[3]); if (matches[2] == '@') { // mentions if (this.mentions && this.mentions.index) { if (query.length) { var foundObject = SearchIndexManager.search(query, this.mentions.index); var foundUsers = []; var user; for (var i = 0, length = this.mentions.users.length; i < length; i++) { user = this.mentions.users[i]; if (foundObject[user.id]) { foundUsers.push(user); } } } else { var foundUsers = this.mentions.users; } if (foundUsers.length) { this.showMentionSuggestions(foundUsers); } else { this.hideSuggestions(); } } else { this.hideSuggestions(); } } else if (!matches[1] && matches[2] == '/') { // commands if (this.commands && this.commands.index) { if (query.length) { var foundObject = SearchIndexManager.search(query, this.commands.index); var foundCommands = []; var command; for (var i = 0, length = this.commands.list.length; i < length; i++) { command = this.commands.list[i]; if (foundObject[command.value]) { foundCommands.push(command); } } } else { var foundCommands = this.commands.list; } if (foundCommands.length) { this.showCommandsSuggestions(foundCommands); } else { this.hideSuggestions(); } } else { this.hideSuggestions(); } } else if (matches[2] == ':') { // emoji EmojiHelper.getPopularEmoji((function (popular) { if (query.length) { var found = EmojiHelper.searchEmojis(query); if (found.length) { var popularFound = [], code, pos; for (var i = 0, len = popular.length; i < len; i++) { code = popular[i].code; pos = found.indexOf(code); if (pos >= 0) { popularFound.push(code); found.splice(pos, 1); if (!found.length) { break; } } } this.showEmojiSuggestions(popularFound.concat(found)); } else { this.hideSuggestions(); } } else { this.showEmojiSuggestions(popular); } }).bind(this)); } } else { delete this.previousQuery; this.hideSuggestions(); } } MessageComposer.prototype.onFocusBlur = function (e) { this.isActive = e.type == 'focus'; if (!this.isActive) { this.cleanRichTextarea(); this.hideSuggestions(); } else { setTimeout(this.checkAutocomplete.bind(this), 100); } if (this.richTextareaEl) { document.execCommand('enableObjectResizing', !this.isActive, !this.isActive); } } 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; } } try { var text = cData.getData('text/plain'); } catch (e) { return true; } setZeroTimeout(this.onChange.bind(this), 0); if (text.length) { document.execCommand('insertText', false, text); return cancelEvent(e); } return true; } MessageComposer.prototype.cleanRichTextarea = function (value, focused) { if (value === undefined) { value = getRichValue(this.richTextareaEl[0]); } if (value.match(/^\s*$/) && this.richTextareaEl.html().length > 0) { this.richTextareaEl.html(''); this.lastLength = 0; this.wasEmpty = true; if (focused) { var self = this; setZeroTimeout(function () { self.focus(); }); } } } 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.replaceChild(document.createTextNode('   '), element); }) } else if (src && !src.match(/img\/blank\.gif/)) { var replacementNode = document.createTextNode(' ' + src + ' '); setTimeout(function () { element.parentNode.replaceChild(replacementNode, element); }, 100); } } 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; var html; if (suffix.length) { this.selId = (this.selId || 0) + 1; html = this.getRichHtml(newValuePrefix) + ' ' + this.getRichHtml(suffix); this.richTextareaEl.html(html); setRichFocus(textarea, $('#composer_sel' + this.selId)[0]); } else { html = this.getRichHtml(newValuePrefix) + ' '; this.richTextareaEl.html(html); setRichFocus(textarea); } } else { var html = this.getEmojiHtml(code); if (window.getSelection) { var sel = window.getSelection(); if (sel.getRangeAt && sel.rangeCount) { var el = document.createElement('div'); el.innerHTML = html; var node = el.firstChild; var range = sel.getRangeAt(0); range.deleteContents(); range.insertNode(document.createTextNode(' ')); range.insertNode(node); range.setStart(node, 0); setTimeout(function() { range = document.createRange(); range.setStartAfter(node); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); }, 0); } } else if (document.selection && document.selection.type != 'Control') { document.selection.createRange().pasteHTML(html); } } } 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); } this.hideSuggestions(); this.onChange(); } MessageComposer.prototype.onMentionsUpdated = function (username) { delete this.previousQuery; if (this.isActive) { this.checkAutocomplete(); } } MessageComposer.prototype.onMentionSelected = function (username) { if (this.richTextareaEl) { var textarea = this.richTextareaEl[0]; if (!this.isActive) { if (!this.restoreSelection()) { setRichFocus(textarea); } } 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 newValuePrefix; if (matches && matches[0]) { newValuePrefix = prefix.substr(0, matches.index) + '@' + username; } else { newValuePrefix = prefix + '@' + username; } textarea.value = newValue; var html; if (suffix.length) { this.selId = (this.selId || 0) + 1; html = this.getRichHtml(newValuePrefix) + ' ' + this.getRichHtml(suffix); this.richTextareaEl.html(html); setRichFocus(textarea, $('#composer_sel' + this.selId)[0]); } else { html = this.getRichHtml(newValuePrefix) + ' '; this.richTextareaEl.html(html); setRichFocus(textarea); } } 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 = prefix.match(/@([A-Za-z0-9\-\+\*_]*)$/); if (matches && matches[0]) { var newValue = prefix.substr(0, matches.index) + '@' + username + ' ' + suffix; var newPos = matches.index + username.length + 2; } else { var newValue = prefix + ':' + username + ': ' + suffix; var newPos = prefix.length + username.length + 2; } textarea.value = newValue; setFieldSelection(textarea, newPos); } this.hideSuggestions(); this.onChange(); } MessageComposer.prototype.onCommandSelected = function (command, isTab) { if (isTab) { if (this.richTextareaEl) { this.richTextareaEl.html(encodeEntities(command) + ' '); setRichFocus(this.richTextareaEl[0]); } else { var textarea = this.textareaEl[0]; textarea.value = command + ' '; setFieldSelection(textarea); } } else { this.onCommandSend(command); } this.hideSuggestions(); this.onChange(); } MessageComposer.prototype.onChange = function (e) { if (this.richTextareaEl) { delete this.keyupStarted; this.textareaEl.val(getRichValue(this.richTextareaEl[0])).trigger('change'); } this.updateInlinePlaceholder(); } 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]) + ':'; } MessageComposer.prototype.setValue = function (text) { if (this.richTextareaEl) { this.richTextareaEl.html(this.getRichHtml(text)); this.lastLength = text.length; this.wasEmpty = !text.length; this.onKeyEvent({type: 'keyup'}); } else { this.textareaEl.val(text); } } MessageComposer.prototype.setFocusedValue = function (parts) { var prefix = parts[0]; var selection = parts[1]; var suffix = parts[2]; if (this.richTextareaEl) { this.selId = (this.selId || 0) + 1; var html = this.getRichHtml(prefix) + '' + this.getRichHtml(selection) + '' + this.getRichHtml(suffix); this.richTextareaEl.html(html); setRichFocus(this.richTextareaEl[0], $('#composer_sel' + this.selId)[0], true); } else { this.textareaEl.val(prefix + selection + suffix); setFieldSelection(this.textareaEl[0], prefix.length, prefix.length + selection.length); } } MessageComposer.prototype.getRichHtml = function (text) { return $('
').text(text).html().replace(/\n/g, '
').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) { setZeroTimeout((function () { setRichFocus(this.richTextareaEl[0]); }).bind(this)); } else { setFieldSelection(this.textareaEl[0]); } } MessageComposer.prototype.blur = function () { if (this.richTextareaEl) { this.richTextareaEl[0].blur(); } else { this.textareaEl[0].blur(); } } MessageComposer.prototype.renderSuggestions = function () { this.autoCompleteWrapEl.show(); this.scroller.reinit(); this.updatePosition(); this.autocompleteShown = true; } MessageComposer.prototype.showEmojiSuggestions = function (codes) { var self = this; setZeroTimeout(function () { self.autoCompleteScope.$apply(function () { self.autoCompleteScope.type = 'emoji'; self.autoCompleteScope.emojiCodes = codes; }); onContentLoaded(function () { self.renderSuggestions(); }); }); } MessageComposer.prototype.showMentionSuggestions = function (users) { var self = this; setZeroTimeout(function () { self.autoCompleteScope.$apply(function () { self.autoCompleteScope.type = 'mentions'; self.autoCompleteScope.mentionUsers = users; }); onContentLoaded(function () { self.renderSuggestions(); }); }); } MessageComposer.prototype.showCommandsSuggestions = function (commands) { var self = this; setZeroTimeout(function () { self.autoCompleteScope.$apply(function () { self.autoCompleteScope.type = 'commands'; self.autoCompleteScope.commands = commands; }); onContentLoaded(function () { self.renderSuggestions(); }); }); } MessageComposer.prototype.showInlineSuggestions = function (botResults) { if (!botResults || !botResults.results.length) { this.hideSuggestions(); return; } var self = this; if (self.autoCompleteScope.type == 'inline' && self.autoCompleteScope.botResults == botResults) { return; } setZeroTimeout(function () { self.autoCompleteScope.$apply(function () { self.autoCompleteScope.type = 'inline'; self.autoCompleteScope.botResults = botResults; }); onContentLoaded(function () { self.renderSuggestions(); }); }); } MessageComposer.prototype.setInlineSuggestions = function (botResults) { this.curInlineResults = botResults; this.checkAutocomplete(); } MessageComposer.prototype.updatePosition = function () { var offset = (this.richTextareaEl || this.textareaEl).offset(); var height = this.scroller.updateHeight(); var width = $((this.richTextareaEl || this.textareaEl)[0].parentNode).outerWidth(); this.autoCompleteWrapEl.css({ top: offset.top - height, left: Config.Mobile ? 0 : offset.left, width: Config.Mobile ? '100%' : width - 2 }); this.scroller.update(); } MessageComposer.prototype.hideSuggestions = function () { // console.trace(); // return; this.autoCompleteWrapEl.hide(); delete this.autocompleteShown; } MessageComposer.prototype.resetTyping = function () { this.lastTyping = 0; this.lastLength = 0; } MessageComposer.prototype.setPlaceholder = function (newPlaceholder) { (this.richTextareaEl || this.textareaEl).attr('placeholder', newPlaceholder); } function Scroller(content, options) { options = options || {}; var classPrefix = options.classPrefix || 'scroller'; this.content = $(content); this.useNano = options.nano !== undefined ? options.nano : !Config.Mobile; this.maxHeight = options.maxHeight; this.minHeight = options.minHeight; if (this.useNano) { this.setUpNano(); } else { this.setUpNative(); } this.updateHeight(); } Scroller.prototype.setUpNano = function () { this.content.wrap('
'); this.scrollable = $(this.content[0].parentNode); this.scroller = $(this.scrollable[0].parentNode); this.wrap = $(this.scroller[0].parentNode); this.scroller.nanoScroller({preventPageScrolling: true, tabIndex: -1}); } Scroller.prototype.setUpNative = function () { this.content.wrap('
'); this.scrollable = $(this.content[0].parentNode); this.scrollable.css({overflow: 'auto'}); if (this.maxHeight) { this.scrollable.css({maxHeight: this.maxHeight}); } if (this.minHeight) { this.scrollable.css({minHeight: this.minHeight}); } } Scroller.prototype.onScroll = function (cb) { var self = this; var scrollable = this.scrollable[0]; this.scrollable.on('scroll', function (e) { if (self.isAnimatedScroll) { return; } cb(scrollable, scrollable.scrollTop); }) } Scroller.prototype.update = function () { if (this.useNano) { $(this.scroller).nanoScroller(); } } Scroller.prototype.reinit = function () { this.scrollTo(0); if (this.useNano) { setTimeout((function () { this.updateHeight(); }).bind(this), 100) } } Scroller.prototype.updateHeight = function () { var height; if (this.useNano) { if (this.maxHeight || this.minHeight) { height = this.content[0].offsetHeight; if (this.maxHeight && height > this.maxHeight) { height = this.maxHeight; } if (this.minHeight && height < this.minHeight) { height = this.minHeight; } this.wrap.css({height: height}); } else { height = this.scroller[0].offsetHeight; } $(this.scroller).nanoScroller(); } else { height = this.scrollable[0].offsetHeight; } return height; } Scroller.prototype.scrollTo = function (scrollTop, animation, cb) { if (animation > 0) { var self = this; this.isAnimatedScroll = true; this.scrollable.animate({scrollTop: scrollTop}, animation, function () { delete self.isAnimatedScroll; if (self.useNano) { $(self.scroller).nanoScroller({flash: true}); } self.scrollable.trigger('scroll'); if (cb) { cb(); } }); } else { this.scrollable[0].scrollTop = scrollTop; if (this.useNano) { $(this.scroller).nanoScroller({flash: true}); } if (cb) { cb(); } } } Scroller.prototype.scrollToNode = function (node) { node = node[0] || node; var elTop = node.offsetTop - 15, elHeight = node.offsetHeight + 30, scrollTop = this.scrollable[0].scrollTop, viewportHeight = this.scrollable[0].clientHeight; if (scrollTop > elTop) { // we are below the node to scroll this.scrollTo(elTop); } else if (scrollTop < elTop + elHeight - viewportHeight) { // we are over the node to scroll this.scrollTo(elTop + elHeight - viewportHeight); } }