/*! * Webogram v0.5.5 - 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 var j var code var shortcut var emoji var row var column var totalColumns var len1 var 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 var 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 var emoticonData var i var x var 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 var emoticonData var spritesheet var pos var categoryIndex var count = popularEmoji.length var i var x var 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 var docID var i var j var len1 var 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 var 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).stop(true).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 var emoticonData var spritesheet var pos var categoryIndex var count = popularEmoji.length var i var x var 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.onDirectionKey = options.onDirectionKey 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|^)(:|@|\/)([\S]*)$/ 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) { // hide scrollbar for both LTR and RTL languages // both scrollbars are hidden inside the paddings // that are overflown outside of view (this.richTextareaEl || this.textareaEl).css({ left: -sbWidth, width: 'calc(100% + ' + (2 * sbWidth) + 'px)', 'padding-left': sbWidth + 2, 'padding-right': sbWidth + 28 }) } } } 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 var mention var code var command var inlineID while (target && target.tagName != 'A') { target = target.parentNode } if (!target) { return cancelEvent(e) } target = $(target) 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, target.attr('data-name')) } 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 var mention var command var 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, currentSel.attr('data-name')) 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) } } // Direction keys when content is empty if ([33, 34, 35, 36, 38, 39].indexOf(e.keyCode) != -1 && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey && this.richTextareaEl && !this.richTextareaEl[0].textContent.length) { return this.onDirectionKey(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 var 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) { 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 var 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 var 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 (!this.richTextareaEl[0]) { return } 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 var src = (element || {}).src || '' var 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(/:([\S]*)$/) 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(/:([\S]*)$/) 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, firstName) { var hasUsername = true; if (username.charAt(0) == '#') { hasUsername = false; username = username.substr(1); firstName = firstName.replace(/\(\)@/, '') } 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(/@([\S]*)$/) var newValuePrefix if (matches && matches[0]) { newValuePrefix = prefix.substr(0, matches.index) + '@' + username } else { newValuePrefix = prefix + '@' + username } var html if (hasUsername) { 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 { this.selId = (this.selId || 0) + 1 html = this.getRichHtml(newValuePrefix) + ' (' + encodeEntities(firstName) + ') ' + this.getRichHtml(suffix) this.richTextareaEl.html(html) setRichFocus(textarea, $('#composer_sel' + this.selId)[0], true) } } 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(/@([\S]*)$/) var newValuePrefix var newValue var newPos var newPosTo if (matches && matches[0]) { newValuePrefix = prefix.substr(0, matches.index) + '@' + username } else { newValuePrefix = prefix + '@' + username } if (hasUsername) { newValue = newValuePrefix + '@' + username + ' ' + suffix newPos = matches.index + username.length + 2 } else { newValue = newValuePrefix + '@' + username + ' (' + firstName + ') ' + suffix newPos = matches.index + username.length + 2 newPosTo = newPos + firstName.length } textarea.value = newValue setFieldSelection(textarea, newPos, newPosTo) } 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 var richValue = getRichValue(this.richTextareaEl[0]) this.textareaEl.val(richValue).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) { var html = $('
').text(text).html() html = html.replace(/\n/g, '
') html = 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)) html = html.replace(/ /g, ' \u00A0').replace(/^ | $/g, '\u00A0') return html } 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 && self.autocompleteShown) { 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 var elHeight = node.offsetHeight + 30 var scrollTop = this.scrollable[0].scrollTop var 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) } }