/*! * Webogram v0.5.6 - 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' /* global Config, templateUrl, onContentLoaded, cancelEvent, dT, setZeroTimeout calcImageInBox, getSelectedText,Scroller, setFieldSelection, scrollToNode, EmojiTooltip, EmojiPanel, MessageComposer, checkDragEvent, checkClick, Image, Clipboard, EmojiHelper, encodeEntities, FB, twttr, gapi, isInDOM, hasOnclick */ /* Directives */ angular.module('myApp.directives', ['myApp.filters']) .constant('shouldFocusOnInteraction', !Config.Navigator.mobile) .directive('myHead', function () { return { restrict: 'AE', templateUrl: templateUrl('head') } }) .directive('myLangFooter', function () { return { restrict: 'AE', templateUrl: templateUrl('lang_footer') } }) .directive('myFooter', function () { return { restrict: 'AE', templateUrl: templateUrl('footer') } }) .directive('myDialog', function () { return { restrict: 'AE', templateUrl: templateUrl('dialog') } }) .directive('myMessage', function ($filter, _) { var dateFilter = $filter('myDate') var dateSplitHtml = '

--- 
 ---
' var unreadSplitHtml = '
' + _('unread_messages_split') + '
' var selectedClass = 'im_message_selected' var focusClass = 'im_message_focus' var unreadClass = 'im_message_unread' var errorClass = 'im_message_error' var pendingClass = 'im_message_pending' return { templateUrl: templateUrl('message'), link: link } function link ($scope, element, attrs) { var selected = false var grouped = false var focused = false var error = false var pending = false var needDate = false var unreadAfter = false var applySelected = function () { if (selected != ($scope.selectedMsgs[$scope.historyMessage.mid] || false)) { selected = !selected element.toggleClass(selectedClass, selected) } } var needDateSplit, unreadAfterSplit var applyGrouped = function () { if (grouped != $scope.historyMessage.grouped) { if (grouped) { element.removeClass(grouped) } grouped = $scope.historyMessage.grouped if (grouped) { element.addClass(grouped) } } if (needDate != ($scope.historyMessage.needDate || false)) { needDate = !needDate if (needDate) { if (needDateSplit) { needDateSplit.show() } else { needDateSplit = $(dateSplitHtml) $('.im_message_date_split_text', needDateSplit).text(dateFilter($scope.historyMessage.date)) if (unreadAfterSplit) { needDateSplit.insertBefore(unreadAfterSplit) } else { needDateSplit.prependTo(element) } } } else { needDateSplit.hide() } } } applySelected() applyGrouped() $scope.$on('messages_select', applySelected) $scope.$on('messages_regroup', applyGrouped) $scope.$on('messages_focus', function (e, focusedMsgID) { if ((focusedMsgID == $scope.historyMessage.mid) != focused) { focused = !focused element.toggleClass(focusClass, focused) } }) var deregisterUnreadAfter if (!$scope.historyMessage.pFlags.out && ($scope.historyMessage.pFlags.unread || $scope.historyMessage.unreadAfter)) { var applyUnreadAfter = function () { if ($scope.peerHistory.peerID != $scope.historyPeer.id) { return } if (unreadAfter != ($scope.historyUnreadAfter == $scope.historyMessage.mid)) { unreadAfter = !unreadAfter if (unreadAfter) { if (unreadAfterSplit) { unreadAfterSplit.show() } else { unreadAfterSplit = $(unreadSplitHtml).prependTo(element) } } else { unreadAfterSplit.hide() if (deregisterUnreadAfter) { deregisterUnreadAfter() } } } } applyUnreadAfter() deregisterUnreadAfter = $scope.$on('messages_unread_after', applyUnreadAfter) } if ($scope.historyMessage.pFlags.unread && $scope.historyMessage.pFlags.out) { element.addClass(unreadClass) var deregisterUnread = $scope.$on('messages_read', function () { if (!$scope.historyMessage.pFlags.unread) { element.removeClass(unreadClass) deregisterUnread() if (deregisterUnreadAfter && !unreadAfter) { deregisterUnreadAfter() } } }) } if ($scope.historyMessage.error || $scope.historyMessage.pending) { var applyPending = function () { if (pending != ($scope.historyMessage.pending || false)) { pending = !pending element.toggleClass(pendingClass, pending) } if (error != ($scope.historyMessage.error || false)) { error = !error element.toggleClass(errorClass, error) } if (!error && !pending) { deregisterPending() } } var deregisterPending = $scope.$on('messages_pending', applyPending) applyPending() } } }) .directive('myMessageBody', function ($compile, AppPeersManager, AppChatsManager, AppUsersManager, AppMessagesManager, AppInlineBotsManager, RichTextProcessor) { var messageMediaCompiled = $compile('
') var messageKeyboardCompiled = $compile('
') var messageSignCompiled = $compile('
') return { link: link, scope: { message: '=myMessageBody' } } function updateMessageText ($scope, element, message) { if ((message.media && message.media.handleMessage) || typeof message.message !== 'string' || !message.message.length) { $('.im_message_text', element).hide() return } var html = AppMessagesManager.wrapMessageText(message.mid) $('.im_message_text', element).html(html.valueOf()) } function updateMessageMedia ($scope, element, message) { if (!message.media) { $('.im_message_media', element).hide() return } var scope = $scope.$new(true) scope.media = message.media scope.messageId = message.mid messageMediaCompiled(scope, function (clonedElement) { $('.im_message_media', element).replaceWith(clonedElement) }) } function updateMessageSignature ($scope, element, message) { if (!message.signID) { $('.im_message_sign', element).hide() return } var scope = $scope.$new(true) scope.signID = message.signID messageSignCompiled(scope, function (clonedElement) { $('.im_message_sign', element).replaceWith(clonedElement) }) } function updateMessageKeyboard ($scope, element, message) { if (!message.reply_markup || message.reply_markup._ != 'replyInlineMarkup') { $('.im_message_keyboard', element).hide() return } var scope = $scope.$new(true) scope.markup = AppMessagesManager.wrapReplyMarkup(message.reply_markup, message.fromID) scope.messageId = message.mid messageKeyboardCompiled(scope, function (clonedElement) { $('.im_message_keyboard', element).replaceWith(clonedElement) }) scope.$on('reply_inline_button_press', function (e, button) { switch (button._) { case 'keyboardButtonSwitchInline': AppInlineBotsManager.switchInlineButtonClick(message.mid, button) break case 'keyboardButtonCallback': AppInlineBotsManager.callbackButtonClick(message.mid, button) break case 'keyboardButtonGame': AppInlineBotsManager.gameButtonClick(message.mid) break } }) } function updateMessageBody ($scope, element, message) { updateMessageText($scope, element, message) updateMessageMedia($scope, element, message) updateMessageSignature($scope, element, message) updateMessageKeyboard($scope, element, message) } function link ($scope, element, attrs) { var message = $scope.message message.dir = true var msgID = message.mid updateMessageBody($scope, element, message) if (message.pending) { var unlink = $scope.$on('messages_pending', function () { if (message.mid != msgID) { updateMessageBody($scope, element, message) unlink() } }) } $scope.$on('message_edit', function (e, data) { if (data.mid == message.mid) { if (data.justMedia) { updateMessageMedia($scope, element, message) } else { updateMessageBody($scope, element, message) } } }) } }) .directive('myMessageViews', function ($filter, AppMessagesManager) { var formatNumberFilter = $filter('formatShortNumber') return { link: link } function updateHtml (views, element) { element.html(formatNumberFilter(views)) } function link ($scope, element, attrs) { var mid = $scope.$eval(attrs.myMessageViews) // console.log(element[0], mid) var views = AppMessagesManager.getMessage(mid).views || 0 updateHtml(views, element) $scope.$on('message_views', function (e, viewData) { if (viewData.mid == mid) { updateHtml(viewData.views, element) } }) } }) .directive('myReplyMarkup', function () { return { templateUrl: templateUrl('reply_markup'), scope: { 'replyMarkup': '=myReplyMarkup' }, link: link } function link ($scope, element, attrs) { var scrollable = $('.reply_markup', element) var scroller = new Scroller(scrollable, { classPrefix: 'reply_markup', maxHeight: 170 }) $scope.buttonClick = function (button) { $scope.$emit('reply_button_press', button) } $scope.$on('ui_keyboard_update', function (e, data) { onContentLoaded(function () { scroller.updateHeight() scroller.scrollTo(0) $scope.$emit('ui_panel_update', {blur: data && data.enabled}) }) }) onContentLoaded(function () { scroller.updateHeight() $scope.$emit('ui_panel_update') }) } }) .directive('myMessageMedia', function () { return { scope: { 'media': '=myMessageMedia', 'messageId': '=messageId' }, templateUrl: templateUrl('message_media') } }) .directive('myMessagePhoto', function (AppPhotosManager) { return { scope: { 'media': '=myMessagePhoto', 'messageId': '=messageId' }, templateUrl: templateUrl('message_attach_photo'), link: function ($scope, element, attrs) { $scope.openPhoto = AppPhotosManager.openPhoto $scope.preloadPhoto = AppPhotosManager.preloadPhoto } } }) .directive('myMessageDocument', function (AppDocsManager) { return { scope: { 'media': '=myMessageDocument', 'messageId': '=messageId' }, templateUrl: templateUrl('message_attach_document'), link: function ($scope, element, attrs) { AppDocsManager.updateDocDownloaded($scope.media.document.id) $scope.docSave = function () { AppDocsManager.saveDocFile($scope.media.document.id) } $scope.docOpen = function () { if (!$scope.media.document.withPreview) { return $scope.docSave() } AppDocsManager.openDoc($scope.media.document.id, $scope.messageId) } $scope.videoOpen = function () { AppDocsManager.openVideo($scope.media.document.id, $scope.messageId) } if ($scope.media.document.file_name) { var fileNameParts = $scope.media.document.file_name.split('.') if (fileNameParts.length > 1) { $scope.media_file_ext = '.' + fileNameParts.pop() $scope.media_file_name_without_ext = fileNameParts.join('.') if (!$scope.media_file_name_without_ext) { $scope.media_file_name_without_ext = $scope.media_file_ext $scope.media_file_ext = '' } } else { $scope.media_file_ext = '' $scope.media_file_name_without_ext = fileNameParts[0] } } } } }) .directive('myMessageGeo', function () { return { scope: { 'media': '=myMessageGeo' }, templateUrl: templateUrl('message_attach_geo') } }) .directive('myMessageVenue', function () { return { scope: { 'media': '=myMessageVenue' }, templateUrl: templateUrl('message_attach_venue') } }) .directive('myMessageContact', function () { return { scope: { 'media': '=myMessageContact' }, templateUrl: templateUrl('message_attach_contact') } }) .directive('myMessageWebpage', function (AppWebPagesManager, AppPhotosManager) { return { scope: { 'media': '=myMessageWebpage', 'messageId': '=messageId' }, templateUrl: templateUrl('message_attach_webpage'), link: function ($scope) { $scope.openPhoto = AppPhotosManager.openPhoto $scope.openEmbed = function ($event) { if ($scope.media.webpage && $scope.media.webpage.embed_url) { AppWebPagesManager.openEmbed($scope.media.webpage.id, $scope.messageId) return cancelEvent($event) } } $scope.$on('webpage_updated', function (e, eventData) { if ($scope.media.webpage && $scope.media.webpage.id == eventData.id) { $scope.$emit('ui_height') } }) } } }) .directive('myMessageGame', function (AppInlineBotsManager, AppMessagesManager) { return { scope: { 'media': '=myMessageGame', 'messageId': '=messageId' }, templateUrl: templateUrl('message_attach_game'), link: function ($scope, element) { $scope.openGame = function () { AppInlineBotsManager.gameButtonClick($scope.messageId) } function updateMessageText (argument) { var message = AppMessagesManager.getMessage($scope.messageId) if (message.message) { var html = AppMessagesManager.wrapMessageText($scope.messageId) $('.im_message_game_message', element).html(html.valueOf()).show() $('.im_message_game_description', element).hide() } else { $('.im_message_game_message', element).html('').hide() $('.im_message_game_description', element).show() } } $scope.$on('message_edit', function (e, data) { if (data.mid == $scope.messageId) { updateMessageText() } }) updateMessageText() } } }) .directive('myMessagePending', function () { return { scope: { 'media': '=myMessagePending' }, templateUrl: templateUrl('message_attach_pending'), link: link } function link ($scope, element, attrs) { if ($scope.media.file_name) { var fileNameParts = $scope.media.file_name.split('.') if (fileNameParts.length > 1) { $scope.media_file_ext = '.' + fileNameParts.pop() $scope.media_file_name_without_ext = fileNameParts.join('.') if (!$scope.media_file_name_without_ext) { $scope.media_file_name_without_ext = $scope.media_file_ext $scope.media_file_ext = '' } } else { $scope.media_file_ext = '' $scope.media_file_name_without_ext = fileNameParts[0] } } } }) .directive('myInlineReplyMarkup', function () { return { templateUrl: templateUrl('reply_markup'), scope: { 'replyMarkup': '=myInlineReplyMarkup' }, link: link } function link ($scope, element, attrs) { $scope.buttonClick = function (button) { $scope.$emit('reply_inline_button_press', button) } } }) .directive('myServiceMessage', function (ErrorService, AppMessagesManager) { return { templateUrl: templateUrl('message_service'), scope: { 'historyMessage': '=myServiceMessage' }, link: link } function link ($scope, element, attrs) { $scope.phoneCallClick = function (messageID) { var message = AppMessagesManager.getMessage(messageID) var userID = AppMessagesManager.getMessagePeer(message) ErrorService.show({ error: { type: 'PHONECALLS_NOT_SUPPORTED', userID: userID } }) } } }) .directive('myShortMessage', function () { return { scope: { message: '=myShortMessage' }, templateUrl: templateUrl('short_message') } }) .directive('myReplyMessage', function (AppMessagesManager, AppPeersManager, $rootScope) { return { templateUrl: templateUrl('reply_message'), scope: {}, link: link } function link ($scope, element, attrs) { if (attrs.watch) { $scope.$parent.$watch(attrs.myReplyMessage, function (mid) { var isEdit = $scope.$parent.$eval(attrs.edit) checkMessage($scope, element, mid, isEdit) }) } else { var mid = $scope.$parent.$eval(attrs.myReplyMessage) var isEdit = $scope.$parent.$eval(attrs.edit) checkMessage($scope, element, mid, isEdit) } } function checkMessage ($scope, element, mid, isEdit) { var message = $scope.replyMessage = AppMessagesManager.wrapSingleMessage(mid) $scope.isEdit = isEdit || false if (message.loading) { var stopWaiting = $scope.$on('messages_downloaded', function (e, mids) { if (mids.indexOf(mid) != -1) { $scope.replyMessage = AppMessagesManager.wrapForDialog(mid) updateMessage($scope, element) stopWaiting() } }) } else { updateMessage($scope, element) } } function updateMessage ($scope, element) { var message = $scope.replyMessage if (!message || message.deleted || !message.to_id) { $(element).remove() return } $scope.thumb = AppMessagesManager.getMessageThumb(message, 42, 42) if (element[0].tagName == 'A') { element.on('click', function () { var peerID = AppMessagesManager.getMessagePeer(message) var peerString = AppPeersManager.getPeerString(peerID) $rootScope.$broadcast('history_focus', {peerString: peerString, messageID: message.mid}) }) } onContentLoaded(function () { $scope.$emit('ui_height') }) } }) .directive('myPinnedMessage', function (AppMessagesManager, AppPeersManager, $rootScope) { return { templateUrl: templateUrl('pinned_message'), scope: {}, link: link } function link ($scope, element, attrs) { var mid = $scope.$parent.$eval(attrs.myPinnedMessage) var message = $scope.pinnedMessage = AppMessagesManager.wrapSingleMessage(mid) if (message.loading) { var stopWaiting = $scope.$on('messages_downloaded', function (e, mids) { if (mids.indexOf(mid) != -1) { $scope.pinnedMessage = AppMessagesManager.wrapForDialog(mid) updateMessage($scope, element) stopWaiting() } }) } else { updateMessage($scope, element) } } function updateMessage ($scope, element) { var message = $scope.pinnedMessage if (!message || message.deleted || !message.to_id) { $(element).remove() return } if (element[0].tagName == 'A') { element.on('click', function () { var peerID = AppMessagesManager.getMessagePeer(message) var peerString = AppPeersManager.getPeerString(peerID) $rootScope.$broadcast('history_focus', {peerString: peerString, messageID: message.mid}) }) } onContentLoaded(function () { $scope.$emit('ui_height') }) } }) .directive('myForwardedMessages', function (AppPhotosManager, AppMessagesManager, AppPeersManager, $rootScope) { return { templateUrl: templateUrl('forwarded_messages'), scope: { 'forwardMessages': '=myForwardedMessages' }, link: link } function link ($scope, element, attrs) { if (attrs.watch) { $scope.$watch('forwardMessages', function () { updateMessages($scope, element) }) } else { updateMessages($scope, element) } } function updateMessages ($scope, element) { var mids = $scope.forwardMessages var length = mids.length var fromID = false var single = length == 1 $scope.thumb = false $scope.singleMessage = false angular.forEach(mids, function (mid) { var message = AppMessagesManager.getMessage(mid) if (fromID === false) { fromID = message.fromID } else { if (fromID !== message.fromID) { fromID = AppMessagesManager.getMessagePeer(message) } } if (single) { $scope.thumb = AppMessagesManager.getMessageThumb(message, 42, 42) $scope.singleMessage = AppMessagesManager.wrapForDialog(mid) } }) $scope.fromID = fromID $scope.count = length onContentLoaded(function () { $scope.$emit('ui_height') }) } }) .directive('myMessageEdited', function (_, $timeout, AppMessagesManager) { var editedLabel = _('message_edited') return { scope: {}, link: link } function link ($scope, element, attrs) { var messageID = $scope.$parent.$eval(attrs.myMessageEdited) if (checkEdited($scope, element, messageID)) { $scope.$on('message_edit', function (e, data) { var messageID = $scope.$parent.$eval(attrs.myMessageEdited) if (data.mid == messageID) { checkEdited($scope, element, messageID) } }) } } function checkEdited ($scope, element, messageID) { var message = AppMessagesManager.getMessage(messageID) if (!message.canBeEdited) { $timeout(function () { $scope.$destroy() element.remove() }) return false } if (message.edit_date) { element.html(editedLabel).show() $timeout(function () { $scope.$destroy() }) return false } return true } }) .directive('myDialogs', function ($modalStack, $transition, $window, $timeout) { return { link: link } function link ($scope, element, attrs) { var dialogsWrap = $('.im_dialogs_wrap', element)[0] var scrollableWrap = $('.im_dialogs_scrollable_wrap', element)[0] var searchField = $('.im_dialogs_search_field', element)[0] var panelWrap = $('.im_dialogs_panel', element)[0] var searchClear = $('.im_dialogs_search_clear', element)[0] var searchFocused = false $(searchField).on('focus blur', function (e) { searchFocused = e.type == 'focus' if (!searchFocused) { $(scrollableWrap).find('.im_dialog_selected').removeClass('im_dialog_selected') if (!searchField.value) { $scope.$emit('ui_dialogs_search_clear') } } }) $scope.$on('dialogs_search_toggle', function () { $(panelWrap).addClass('im_dialogs_panel_search') $scope.$broadcast('ui_dialogs_search') $($window).scrollTop(0) $timeout(function () { setFieldSelection(searchField) }) }) $scope.$on('search_clear', function () { $(panelWrap).removeClass('im_dialogs_panel_search') $scope.$broadcast('ui_dialogs_search') }) $(document).on('keydown', onKeyDown) $scope.$on('$destroy', function () { $(document).off('keydown', onKeyDown) }) $scope.$on('ui_dialogs_change', function () { onContentLoaded(function () { var selectedDialog = $(scrollableWrap).find('.active a.im_dialog')[0] if (selectedDialog) { scrollToNode(scrollableWrap, selectedDialog.parentNode, dialogsWrap) } }) }) function onKeyDown (e) { if (!searchFocused && $modalStack.getTop()) { return true } var currentSelected, nextDialogWrap, dialogWraps if (e.keyCode == 36 && !e.shiftKey && !e.ctrlKey && e.altKey) { // Alt + Home currentSelected = $(scrollableWrap).find('.im_dialog_wrap a') if (currentSelected.length) { $(currentSelected[0]).trigger('mousedown') scrollableWrap.scrollTop = 0 $(dialogsWrap).nanoScroller({flash: true}) } return cancelEvent(e) } if (e.keyCode == 27 || (e.keyCode == 9 && e.shiftKey && !e.ctrlKey && !e.metaKey)) { // ESC or Shift + Tab if (!searchFocused) { setFieldSelection(searchField) if (searchField.value) { searchField.select() } } else if (searchField.value) { $(searchClear).trigger('click') } else { $scope.$emit('esc_no_more') // Strange Chrome bug, when field doesn't get blur, but becomes inactive after location change setTimeout(function () { searchField.blur() setTimeout(function () { searchField.focus() }, 0) }, 100) } return cancelEvent(e) } if (searchFocused && e.keyCode == 13 && !Config.Navigator.mobile) { // Enter currentSelected = $(scrollableWrap).find('.im_dialog_selected')[0] || $(scrollableWrap).find('.im_dialog_wrap a')[0] if (currentSelected && !$(currentSelected).hasClass('disabled')) { $(currentSelected).trigger('mousedown') } return cancelEvent(e) } if ( (!Config.Navigator.osX && // No Mac users e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey && e.keyCode >= 49 && e.keyCode <= 57) || // Alt + Shift + # , switch to conversation # where # is in [1..9] (Config.Navigator.osX && // Mac users only e.ctrlKey && e.shiftKey && !e.metaKey && !e.altKey && e.keyCode >= 49 && e.keyCode <= 57)) { // Ctrl + Shift + # , switch to conversation # where # is in [1..9] var dialogNumber = e.keyCode - 49 dialogWraps = $(scrollableWrap).find('.im_dialog_wrap') nextDialogWrap = dialogWraps[dialogNumber] if (nextDialogWrap) { $(nextDialogWrap).find('a').trigger('mousedown') scrollToNode(scrollableWrap, nextDialogWrap, dialogsWrap) } return cancelEvent(e) } var next var prev, skip var ctrlTabSupported = Config.Modes.packed if (e.keyCode == 40 || e.keyCode == 38) { // UP, DOWN next = e.keyCode == 40 prev = !next skip = !e.shiftKey && e.altKey } else if (ctrlTabSupported && e.keyCode == 9 && e.ctrlKey && !e.metaKey) { // Ctrl + Tab, Shift + Ctrl + Tab next = !e.shiftKey prev = !next skip = true } if (next || prev) { if (!skip && (!searchFocused || e.metaKey)) { return true } currentSelected = (!skip && $(scrollableWrap).find('.im_dialog_selected')[0]) || $(scrollableWrap).find('.active a.im_dialog')[0] var currentSelectedWrap = currentSelected && currentSelected.parentNode if (currentSelectedWrap) { nextDialogWrap = currentSelected[next ? 'nextSibling' : 'previousSibling'] if (!nextDialogWrap || !nextDialogWrap.className || nextDialogWrap.className.indexOf('im_dialog_wrap') == -1) { dialogWraps = $(scrollableWrap).find('.im_dialog_wrap') var pos = dialogWraps.index(currentSelected.parentNode) var nextPos = pos + (next ? 1 : -1) nextDialogWrap = dialogWraps[nextPos] } } else { dialogWraps = $(scrollableWrap).find('.im_dialog_wrap') if (next) { nextDialogWrap = dialogWraps[0] } else { nextDialogWrap = dialogWraps[dialogWraps.length - 1] } } if (skip) { if (nextDialogWrap) { $(nextDialogWrap).find('a').trigger('mousedown') } } else { if (currentSelectedWrap && nextDialogWrap) { $(currentSelectedWrap).find('a').removeClass('im_dialog_selected') } if (nextDialogWrap) { $(nextDialogWrap).find('a').addClass('im_dialog_selected') } } if (nextDialogWrap) { scrollToNode(scrollableWrap, nextDialogWrap, dialogsWrap) } return cancelEvent(e) } } } }) .directive('myDialogsList', function ($window, $timeout) { return { link: link } function link ($scope, element, attrs) { var dialogsWrap = $('.im_dialogs_wrap', element)[0] var dialogsColWrap = $('.im_dialogs_col_wrap')[0] var scrollableWrap = $('.im_dialogs_scrollable_wrap', element)[0] var headWrap = $('.tg_page_head')[0] var panelWrapSelector = Config.Mobile && attrs.modal ? '.mobile_modal_body .im_dialogs_panel' : '.im_dialogs_panel' var panelWrap = $(panelWrapSelector)[0] var footer = $('.footer_wrap')[0] var moreNotified = false onContentLoaded(function () { $(dialogsWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1, iOSNativeScrolling: true}) }) var updateScroller = function () { onContentLoaded(function () { $(dialogsWrap).nanoScroller() }) } $scope.$on('ui_dialogs_prepend', updateScroller) $scope.$on('ui_dialogs_search', updateSizes) $scope.$on('ui_dialogs_update', updateSizes) $scope.$on('ui_dialogs_append', function () { onContentLoaded(function () { updateScroller() moreNotified = false $timeout(function () { $(scrollableWrap).trigger('scroll') }) }) }) $scope.$on('ui_dialogs_change', function () { onContentLoaded(function () { updateScroller() moreNotified = false $timeout(function () { $(scrollableWrap).trigger('scroll') }) }) }) $(scrollableWrap).on('scroll', function (e) { if (!element.is(':visible')) return // console.log('scroll', moreNotified) if (!moreNotified && scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) { // console.log('emit need more') $scope.$emit('dialogs_need_more') moreNotified = true } }) function updateSizes () { if (!panelWrap || !panelWrap.offsetHeight) { panelWrap = $(panelWrapSelector)[0] } if (attrs.modal) { var height = $($window).height() - (panelWrap ? panelWrap.offsetHeight : 49) - (Config.Mobile ? 46 : 100) height = Math.min(Config.Mobile ? 350 : 450, height) $(element).css({height: height}) updateScroller() return } if (!headWrap || !headWrap.offsetHeight) { headWrap = $('.tg_page_head')[0] } if (!footer || !footer.offsetHeight) { footer = $('.footer_wrap')[0] } if (!dialogsColWrap || !dialogsColWrap.offsetHeight) { dialogsColWrap = $('.im_dialogs_col_wrap')[0] } var footerHeight = footer ? footer.offsetHeight : 0 if (footerHeight) { footerHeight++ // Border bottom } $(element).css({ height: $($window).height() - footerHeight - (headWrap ? headWrap.offsetHeight : 48) - (panelWrap ? panelWrap.offsetHeight : 58) - parseInt($(dialogsColWrap).css('paddingBottom') || 0) }) updateScroller() } $($window).on('resize', updateSizes) updateSizes() setTimeout(updateSizes, 1000) } }) .directive('myContactsList', function ($window, $timeout) { return { link: link } function link ($scope, element, attrs) { var searchWrap = $('.contacts_modal_search')[0] var panelWrap = $('.contacts_modal_panel')[0] var contactsWrap = $('.contacts_wrap', element)[0] onContentLoaded(function () { $(contactsWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1, iOSNativeScrolling: true}) updateSizes() }) function updateSizes () { $(element).css({ height: $($window).height() - ((panelWrap && panelWrap.offsetHeight) || 0) - ((searchWrap && searchWrap.offsetHeight) || 0) - (Config.Mobile ? 64 : 200) }) $(contactsWrap).nanoScroller() } $($window).on('resize', updateSizes) $scope.$on('contacts_change', function () { onContentLoaded(updateSizes) }) } }) .directive('myCountriesList', function ($window, $timeout) { return { link: link } function link ($scope, element, attrs) { var searchWrap = $('.countries_modal_search')[0] var panelWrap = $('.countries_modal_panel')[0] var countriesWrap = $('.countries_wrap', element)[0] onContentLoaded(function () { $(countriesWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1, iOSNativeScrolling: true}) updateSizes() }) function updateSizes () { $(element).css({ height: $($window).height() - ((panelWrap && panelWrap.offsetHeight) || 0) - ((searchWrap && searchWrap.offsetHeight) || 0) - (Config.Mobile ? 46 + 18 : 200) }) $(countriesWrap).nanoScroller() } $($window).on('resize', updateSizes) $scope.$on('contacts_change', function () { onContentLoaded(updateSizes) }) } }) .directive('mySessionsList', function ($window, $timeout) { return { link: link } function link ($scope, element, attrs) { var sessionsWrap = $('.sessions_wrap', element)[0] onContentLoaded(function () { $(sessionsWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1, iOSNativeScrolling: true}) updateSizes() }) function updateSizes () { $(element).css({ height: Math.min(760, $($window).height() - (Config.Mobile ? 46 + 18 : 200)) }) $(sessionsWrap).nanoScroller() } $($window).on('resize', updateSizes) } }) .directive('myStickersList', function ($window, $timeout) { return { link: link } function link ($scope, element, attrs) { var stickersWrap = $('.stickerset_wrap', element)[0] onContentLoaded(function () { $(stickersWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1, iOSNativeScrolling: true}) updateSizes() }) function updateSizes () { $(element).css({ height: Math.min(600, $($window).height() - (Config.Mobile ? 46 + 18 : 200)) }) $(stickersWrap).nanoScroller() } $($window).on('resize', updateSizes) } }) .directive('myHistory', function ($window, $timeout, $rootScope, $transition) { return { link: link } function link ($scope, element, attrs) { var historyWrap = $('.im_history_wrap', element)[0] var historyMessagesEl = $('.im_history_messages', element)[0] var historyEl = $('.im_history', element)[0] var scrollableWrap = $('.im_history_scrollable_wrap', element)[0] var scrollable = $('.im_history_scrollable', element)[0] var emptyWrapEl = $('.im_history_empty_wrap', element)[0] var bottomPanelWrap = $('.im_bottom_panel_wrap', element)[0] var sendFormWrap = $('.im_send_form_wrap', element)[0] var headWrap = $('.tg_page_head')[0] var footer = $('.footer_wrap')[0] var sendForm = $('.im_send_form', element)[0] var moreNotified = false var lessNotified = false onContentLoaded(function () { scrollableWrap.scrollTop = scrollableWrap.scrollHeight }) $(historyWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1, iOSNativeScrolling: true}) var updateScroller = function (delay) { // console.trace('scroller update', delay) $timeout(function () { if (!$(scrollableWrap).hasClass('im_history_to_bottom')) { $(historyWrap).nanoScroller() } }, delay || 0) } var transform = false var trs = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform'] var i for (i = 0; i < trs.length; i++) { if (trs[i] in historyMessagesEl.style) { transform = trs[i] break } } var animated = transform && false ? true : false // ? var curAnimation = false $scope.$on('ui_history_append_new', function (e, options) { if (!atBottom && !options.my) { onContentLoaded(function () { $(historyWrap).nanoScroller() }) return } if (options.idleScroll) { onContentLoaded(function () { $(historyWrap).nanoScroller() changeScroll(true) }) return } var curAnimated = animated && !$rootScope.idle.isIDLE && historyMessagesEl.clientHeight > 0 var wasH if (curAnimated) { wasH = scrollableWrap.scrollHeight } else { var pr = parseInt($(scrollableWrap).css('paddingRight')) $(scrollable).css({bottom: 0, paddingRight: pr}) $(scrollableWrap).addClass('im_history_to_bottom') } onContentLoaded(function () { if (curAnimated) { curAnimation = true $(historyMessagesEl).removeClass('im_history_appending') scrollableWrap.scrollTop = scrollableWrap.scrollHeight $(historyMessagesEl).css(transform, 'translate(0px, ' + (scrollableWrap.scrollHeight - wasH) + 'px)') $(historyWrap).nanoScroller() var styles = {} styles[transform] = 'translate(0px, 0px)' $(historyMessagesEl).addClass('im_history_appending') $transition($(historyMessagesEl), styles).then(function () { curAnimation = false $(historyMessagesEl).removeClass('im_history_appending') updateBottomizer() }) } else { $(scrollableWrap).removeClass('im_history_to_bottom') $(scrollable).css({bottom: '', paddingRight: 0}) scrollableWrap.scrollTop = scrollableWrap.scrollHeight updateBottomizer() } }) }) function changeScroll (noFocus, animated) { var unreadSplit var focusMessage var newScrollTop = false var afterScrollAdd // console.trace(dT(), 'change scroll', animated) if (!noFocus && (focusMessage = $('.im_message_focus:visible', scrollableWrap)[0])) { // console.log(dT(), 'change scroll to focus', focusMessage) var ch = scrollableWrap.clientHeight var st = scrollableWrap.scrollTop var ot = focusMessage.offsetTop var h = focusMessage.clientHeight if (!st || st + ch < ot || st > ot + h || animated) { newScrollTop = Math.max(0, ot - Math.floor(ch / 2) + 26) } atBottom = false afterScrollAdd = function () { var unfocusMessagePromise = $(focusMessage).data('unfocus_promise') if (unfocusMessagePromise) { $timeout.cancel(unfocusMessagePromise) $(focusMessage).removeClass('im_message_focus_active') } $timeout(function () { $(focusMessage).addClass('im_message_focus_active') unfocusMessagePromise = $timeout(function () { $(focusMessage).removeClass('im_message_focus_active') $(focusMessage).data('unfocus_promise', false) }, 2800) $(focusMessage).data('unfocus_promise', unfocusMessagePromise) }) } } else if (unreadSplit = $('.im_message_unread_split:visible', scrollableWrap)[0]) { // console.log(dT(), 'change scroll unread', unreadSplit.offsetTop) newScrollTop = Math.max(0, unreadSplit.offsetTop - 52) atBottom = false } else { // console.log(dT(), 'change scroll bottom') newScrollTop = scrollableWrap.scrollHeight atBottom = true } if (newScrollTop !== false) { var afterScroll = function () { updateScroller() $timeout(function () { $(scrollableWrap).trigger('scroll') scrollTopInitial = scrollableWrap.scrollTop }) if (afterScrollAdd) { afterScrollAdd() } } if (animated) { $(scrollableWrap).animate({scrollTop: newScrollTop}, 200, afterScroll) } else { scrollableWrap.scrollTop = newScrollTop afterScroll() } } } $scope.$on('history_direction_key', function (e, data) { var newScrollTop = false console.warn('scroll top', data.keyCode) switch (data.keyCode) { case 33: // page up newScrollTop = scrollableWrap.scrollTop - scrollableWrap.clientHeight break case 34: // page down newScrollTop = scrollableWrap.scrollTop + scrollableWrap.clientHeight break case 36: // home newScrollTop = 0 break case 35: // end newScrollTop = scrollableWrap.scrollHeight break } if (newScrollTop !== false) { $(scrollableWrap).stop().animate({scrollTop: newScrollTop}, 200) } }) $scope.$on('ui_history_change', function () { var pr = parseInt($(scrollableWrap).css('paddingRight')) $(scrollableWrap).addClass('im_history_to_bottom') scrollableWrap.scrollHeight // Some strange Chrome bug workaround $(scrollable).css({bottom: 0, paddingRight: pr}) onContentLoaded(function () { $(scrollableWrap).removeClass('im_history_to_bottom') $(scrollable).css({bottom: '', paddingRight: ''}) updateSizes(true) moreNotified = false lessNotified = false changeScroll() }) }) $scope.$on('ui_history_change_scroll', function (e, animated) { onContentLoaded(function () { changeScroll(false, animated) }) }) $scope.$on('ui_history_focus', function () { if (!atBottom) { // console.log(dT(), 'scroll history focus') scrollableWrap.scrollTop = scrollableWrap.scrollHeight updateScroller() atBottom = true } }) $scope.$on('ui_history_prepend', function () { var sh = scrollableWrap.scrollHeight var st = scrollableWrap.scrollTop var pr = parseInt($(scrollableWrap).css('paddingRight')) var ch = scrollableWrap.clientHeight $(scrollableWrap).addClass('im_history_to_bottom') scrollableWrap.scrollHeight // Some strange Chrome bug workaround $(scrollable).css({bottom: -(sh - st - ch), paddingRight: pr}) var upd = function () { $(scrollableWrap).removeClass('im_history_to_bottom') $(scrollable).css({bottom: '', paddingRight: ''}) if (scrollTopInitial >= 0) { changeScroll() } else { // console.log('change scroll prepend') scrollableWrap.scrollTop = st + scrollableWrap.scrollHeight - sh } updateBottomizer() moreNotified = false $timeout(function () { if (scrollableWrap.scrollHeight != sh) { $(scrollableWrap).trigger('scroll') } }) clearTimeout(timer) unreg() } var timer = setTimeout(upd, 0) var unreg = $scope.$on('$viewContentLoaded', upd) }) $scope.$on('ui_history_append', function () { var sh = scrollableWrap.scrollHeight onContentLoaded(function () { atBottom = false updateBottomizer() lessNotified = false if (scrollTopInitial >= 0) { changeScroll() } $timeout(function () { if (scrollableWrap.scrollHeight != sh) { $(scrollableWrap).trigger('scroll') } }) }) }) $scope.$on('ui_panel_update', function (e, data) { updateSizes() onContentLoaded(function () { updateSizes() if (data && data.blur) { $scope.$broadcast('ui_message_blur') } else if (!getSelectedText()) { $scope.$broadcast('ui_message_send') } $timeout(function () { $(scrollableWrap).trigger('scroll') }) }) }) $scope.$on('ui_selection_clear', function () { if (window.getSelection) { if (window.getSelection().empty) { // Chrome window.getSelection().empty() } else if (window.getSelection().removeAllRanges) { // Firefox window.getSelection().removeAllRanges() } } else if (document.selection) { // IE? document.selection.empty() } }) $scope.$on('ui_editor_resize', updateSizes) $scope.$on('ui_height', function () { onContentLoaded(updateSizes) // updateSizes() }) var atBottom = true var scrollTopInitial = -1 $(scrollableWrap).on('scroll', function (e) { if (!element.is(':visible') || $(scrollableWrap).hasClass('im_history_to_bottom') || curAnimation) { return } var st = scrollableWrap.scrollTop atBottom = st >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight if (scrollTopInitial >= 0 && scrollTopInitial != st) { scrollTopInitial = -1 } if (!moreNotified && st <= 300) { moreNotified = true $scope.$emit('history_need_more') } else if (!lessNotified && st >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) { lessNotified = true $scope.$emit('history_need_less') } }) function updateSizes (heightOnly) { if (!element.is(':visible') && !$(element[0].parentNode.parentNode).is(':visible')) { return } if ($(sendFormWrap).is(':visible')) { $(sendFormWrap).css({ height: $(sendForm).height() }) } if (!headWrap || !headWrap.offsetHeight) { headWrap = $('.tg_page_head')[0] } if (!footer || !footer.offsetHeight) { footer = $('.footer_wrap')[0] } var footerHeight = footer ? footer.offsetHeight : 0 if (footerHeight) { footerHeight++ // Border bottom } var historyH = $($window).height() - bottomPanelWrap.offsetHeight - (headWrap ? headWrap.offsetHeight : 48) - footerHeight $(historyWrap).css({ height: historyH }) updateBottomizer() if (heightOnly === true) return if (atBottom) { onContentLoaded(function () { // console.log('change scroll bottom') scrollableWrap.scrollTop = scrollableWrap.scrollHeight updateScroller() }) } updateScroller(100) } function updateBottomizer () { $(historyMessagesEl).css({marginTop: 0}) var marginTop = scrollableWrap.offsetHeight - historyMessagesEl.offsetHeight - emptyWrapEl.offsetHeight - (Config.Mobile ? 0 : 39) if (historyMessagesEl.offsetHeight > 0 && marginTop > 0) { $(historyMessagesEl).css({marginTop: marginTop}) } $(historyWrap).nanoScroller() } $($window).on('resize', updateSizes) updateSizes() onContentLoaded(updateSizes) } }) .directive('mySendForm', function (_, $q, $timeout, $compile, $modalStack, $http, $interpolate, Storage, AppStickersManager, AppDocsManager, ErrorService, AppInlineBotsManager, FileManager, shouldFocusOnInteraction) { return { link: link, scope: { draftMessage: '=', mentions: '=', commands: '=' } } 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 var dragTimeout var submitBtn = $('.im_submit', element)[0] var stickerImageCompiled = $compile('') var cachedStickerImages = {} var emojiTooltip = new EmojiTooltip(emojiButton, { getStickers: function (callback) { AppStickersManager.getStickers().then(callback) }, getStickerImage: function (element, docID) { var category = element.attr('data-category') var cached = cachedStickerImages[docID] if (cached && !isInDOM(cached[0])) { cached.attr('data-category', category) element.replaceWith(cached) return } var scope = $scope.$new(true) scope.document = AppDocsManager.getDoc(docID) stickerImageCompiled(scope, function (clonedElement) { cachedStickerImages[docID] = clonedElement clonedElement.attr('data-category', category) element.replaceWith(clonedElement) }) }, onStickersetSelected: function (stickerset) { AppStickersManager.openStickersetLink(stickerset) }, onEmojiSelected: function (code) { $scope.$apply(function () { composer.onEmojiSelected(code) }) }, onStickerSelected: function (docID) { $scope.$apply(function () { $scope.draftMessage.sticker = docID }) }, langpack: { im_emoji_tab: _('im_emoji_tab'), im_stickers_tab: _('im_stickers_tab') } }) $scope.$on('stickers_changed', function () { emojiTooltip.onStickersChanged() }) var composerEmojiPanel if (emojiPanel) { composerEmojiPanel = new EmojiPanel(emojiPanel, { onEmojiSelected: function (code) { composer.onEmojiSelected(code) } }) } var composer = new MessageComposer(messageField, { onTyping: function () { $scope.$emit('ui_typing') }, getSendOnEnter: function () { return sendOnEnter }, dropdownDirective: function (element, callback) { var scope = $scope.$new(true) var clonedElement = $compile('
')(scope, function (clonedElement, scope) { element.replaceWith(clonedElement) callback(scope, clonedElement) }) }, mentions: $scope.mentions, commands: $scope.commands, onMessageSubmit: onMessageSubmit, onDirectionKey: onDirectionKey, onInlineResultSend: onInlineResultSend, onFilePaste: onFilePaste, onCommandSend: function (command) { $scope.$apply(function () { $scope.draftMessage.command = command }) } }) var richTextarea = composer.richTextareaEl && composer.richTextareaEl[0] if (richTextarea) { $(richTextarea).on('keydown keyup', updateHeight) } $scope.$on('inline_results', function (e, inlineResults) { var w = Config.Mobile ? $(window).width() : (messageFieldWrap.offsetWidth || 382) - 2 var h = 80 if (inlineResults) { AppInlineBotsManager.regroupWrappedResults(inlineResults.results, w, h) } setZeroTimeout(function () { composer.setInlineSuggestions(inlineResults) }) }) $scope.$on('inline_placeholder', function (e, data) { composer.setInlinePlaceholder(data.prefix, data.placeholder) }) 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() $(submitBtn).on('mousedown touchstart', onMessageSubmit) function onMessageSubmit (e) { $timeout(function () { updateValue() $scope.draftMessage.send() composer.resetTyping() if (composerEmojiPanel) { composerEmojiPanel.update() } composer.hideSuggestions() }, shouldFocusOnInteraction ? 0 : 100) return cancelEvent(e) } function onInlineResultSend (qID) { $scope.$apply(function () { $scope.draftMessage.inlineResultID = qID }) } function onDirectionKey (e) { if (e.keyCode == 38) { $scope.$emit('last_message_edit') return cancelEvent(e) } $scope.$emit('history_direction_key', e) return true } function updateValue () { if (richTextarea) { composer.onChange() updateHeight() } } var height = richTextarea && richTextarea.offsetHeight function updateHeight () { var newHeight = richTextarea.offsetHeight if (height != newHeight) { height = newHeight $scope.$emit('ui_editor_resize') } } function onKeyDown (e) { if (e.keyCode == 9 && !e.shiftKey && !e.ctrlKey && !e.metaKey && !$modalStack.getTop()) { // TAB composer.focus() return cancelEvent(e) } } $(document).on('keydown', onKeyDown) $('body').on('dragenter dragleave dragover drop', onDragDropEvent) $(document).on('paste', onPasteEvent) if (shouldFocusOnInteraction) { $scope.$on('ui_peer_change', focusField) $scope.$on('ui_history_focus', focusField) $scope.$on('ui_history_change', focusField) } $scope.$on('ui_peer_change', composer.resetTyping.bind(composer)) $scope.$on('ui_peer_draft', function (e, options) { options = options || {} var isBroadcast = $scope.draftMessage.isBroadcast composer.setPlaceholder(_(isBroadcast ? 'im_broadcast_field_placeholder_raw' : 'im_message_field_placeholder_raw')) if (options.customSelection) { composer.setFocusedValue(options.customSelection) updateHeight() } else { if (richTextarea) { composer.setValue($scope.draftMessage.text || '') updateHeight() } if (shouldFocusOnInteraction || (options && options.focus)) { composer.focus() } } onContentLoaded(function () { composer.checkAutocomplete(true) }) if (emojiTooltip && Config.Mobile) { emojiTooltip.hide() } }) $scope.$on('ui_peer_reply', function () { onContentLoaded(function () { $scope.$emit('ui_editor_resize') if (shouldFocusOnInteraction) { composer.focus() } }) }) $scope.$on('mentions_update', function () { composer.onMentionsUpdated() }) $scope.$on('ui_message_before_send', function () { updateValue() }) $scope.$on('ui_message_send', function () { if (shouldFocusOnInteraction) { focusField() } }) $scope.$on('ui_message_blur', function () { composer.blur() }) function focusField () { onContentLoaded(function () { composer.focus() }) } function onFilePaste (blob) { var mimeType = blob.type || '' var fileUrlPromise = $q.when(false) if (['image/jpeg', 'image/gif', 'image/png', 'image/bmp'].indexOf(mimeType) >= 0) { fileUrlPromise = FileManager.getFileCorrectUrl(blob, mimeType) } fileUrlPromise.then(function (fileUrl) { fileUrl = fileUrl || false ErrorService.confirm({type: 'FILE_CLIPBOARD_PASTE', fileUrl: fileUrl}).then(function () { $scope.draftMessage.files = [blob] $scope.draftMessage.isMedia = true }) }) } function onPasteEvent (e) { var cData = (e.originalEvent || e).clipboardData var items = (cData && cData.items) || [] var files = [] var i, file for (i = 0; i < items.length; i++) { if (items[i].kind == 'file') { file = items[i].getAsFile() files.push(file) } } if (files.length > 0) { if (files.length == 1) { return onFilePaste(files[0]) } ErrorService.confirm({type: 'FILES_CLIPBOARD_PASTE', files: files}).then(function () { $scope.draftMessage.files = files $scope.draftMessage.isMedia = true }) } } function onDragDropEvent (e) { var dragStateChanged = false if (!dragStarted || dragStarted == 1) { dragStarted = checkDragEvent(e) ? 2 : 1 dragStateChanged = true } if (dragStarted == 2) { if (dragTimeout) { setTimeout(function () { clearTimeout(dragTimeout) dragTimeout = false }, 0) } if (e.type == 'dragenter' || e.type == 'dragover') { if (dragStateChanged) { if (!Config.Mobile) { $(emojiButton).hide() } $(dropbox) .css({height: messageFieldWrap.offsetHeight + 2, width: messageFieldWrap.offsetWidth}) .show() } } else { if (e.type == 'drop') { $scope.$apply(function () { $scope.draftMessage.files = Array.prototype.slice.call(e.originalEvent.dataTransfer.files) $scope.draftMessage.isMedia = true }) } dragTimeout = setTimeout(function () { $(dropbox).hide() if (!Config.Mobile) { $(emojiButton).show() } dragStarted = false dragTimeout = false }, 300) } } return cancelEvent(e) } $scope.$on('$destroy', function cleanup () { $(document).off('paste', onPasteEvent) $(document).off('keydown', onKeyDown) $('body').off('dragenter dragleave dragover drop', onDragDropEvent) $(submitBtn).off('mousedown touchstart') fileSelects.off('change') }) if (shouldFocusOnInteraction) { focusField() } } }) .directive('myLoadThumb', function (MtpApiFileManager, FileManager) { return { link: link, scope: { thumb: '=' } } function link ($scope, element, attrs) { var counter = 0 var cachedBlob = MtpApiFileManager.getCachedFile( $scope.thumb && $scope.thumb.location && !$scope.thumb.location.empty && $scope.thumb.location ) if (cachedBlob) { element.attr('src', FileManager.getUrl(cachedBlob, 'image/jpeg')) } if ($scope.thumb && $scope.thumb.width && $scope.thumb.height) { element.attr('width', $scope.thumb.width) element.attr('height', $scope.thumb.height) } var stopWatching = $scope.$watchCollection('thumb.location', function (newLocation) { if ($scope.thumb && $scope.thumb.width && $scope.thumb.height) { element.attr('width', $scope.thumb.width) element.attr('height', $scope.thumb.height) $scope.$emit('ui_height') } // console.log('new loc', newLocation, arguments) var counterSaved = ++counter if (!newLocation || newLocation.empty) { element.attr('src', ($scope.thumb && $scope.thumb.placeholder) || 'img/blank.gif') cleanup() return } var cachedBlob = MtpApiFileManager.getCachedFile(newLocation) if (cachedBlob) { element.attr('src', FileManager.getUrl(cachedBlob, 'image/jpeg')) cleanup() return } if (!element.attr('src')) { element.attr('src', $scope.thumb.placeholder || 'img/blank.gif') } MtpApiFileManager.downloadSmallFile($scope.thumb.location).then(function (blob) { if (counterSaved == counter) { element.attr('src', FileManager.getUrl(blob, 'image/jpeg')) cleanup() } }, function (e) { console.log('Download image failed', e, $scope.thumb.location, element[0]) if (counterSaved == counter) { element.attr('src', $scope.thumb.placeholder || 'img/blank.gif') cleanup() } }) }) var cleanup = attrs.watch ? angular.noop : function () { setTimeout(function () { $scope.$destroy() stopWatching() }, 0) } } }) .directive('myLoadFullPhoto', function (MtpApiFileManager, FileManager, _) { return { link: link, transclude: true, templateUrl: templateUrl('full_photo'), scope: { fullPhoto: '=', thumbLocation: '=' } } function link ($scope, element, attrs) { var imgElement = $('img', element)[0] var resizeElements = $('.img_fullsize_with_progress_wrap', element) .add('.img_fullsize_progress_wrap', element) .add($(imgElement)) var resize = function () { resizeElements.css({width: $scope.fullPhoto.width, height: $scope.fullPhoto.height}) $scope.$emit('ui_height', true) } var jump = 0 $scope.$watchCollection('fullPhoto.location', function () { var cachedBlob = MtpApiFileManager.getCachedFile($scope.thumbLocation) var curJump = ++jump if (cachedBlob) { imgElement.src = FileManager.getUrl(cachedBlob, 'image/jpeg') resize() } else { imgElement.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' } if (!$scope.fullPhoto.location) { return } var apiPromise if ($scope.fullPhoto.size) { var inputLocation = { _: 'inputFileLocation', volume_id: $scope.fullPhoto.location.volume_id, local_id: $scope.fullPhoto.location.local_id, secret: $scope.fullPhoto.location.secret } apiPromise = MtpApiFileManager.downloadFile($scope.fullPhoto.location.dc_id, inputLocation, $scope.fullPhoto.size) } else { apiPromise = MtpApiFileManager.downloadSmallFile($scope.fullPhoto.location) } $scope.progress = {enabled: true, percent: 0} apiPromise.then(function (blob) { if (curJump == jump) { $scope.progress.enabled = false imgElement.src = FileManager.getUrl(blob, 'image/jpeg') resize() } }, function (e) { console.log('Download image failed', e, $scope.fullPhoto.location) $scope.progress.enabled = false if (e && e.type == 'FS_BROWSER_UNSUPPORTED') { $scope.error = {html: _('error_browser_no_local_file_system_image_md', { 'moz-link': '{1}', 'chrome-link': '{1}', 'telegram-link': '{1}' })} } else { $scope.error = {text: _('error_image_download_failed'), error: e} } }, function (progress) { $scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)) }) }) resize() } }) .directive('myLoadVideo', function ($sce, AppDocsManager, ErrorService, _) { return { link: link, transclude: true, templateUrl: templateUrl('full_video'), scope: { video: '=' } } function link ($scope, element, attrs) { var downloadPromise = AppDocsManager.downloadDoc($scope.video.id) downloadPromise.then(function () { $scope.$emit('ui_height') onContentLoaded(function () { var videoEl = $('video', element)[0] if (videoEl) { var errorAlready = false var onVideoError = function (event) { if (errorAlready) { return } if (!event.target || !event.target.error || event.target.error.code == event.target.error.MEDIA_ERR_DECODE || event.target.error.code == event.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED) { errorAlready = true ErrorService.show({ error: { type: 'MEDIA_TYPE_NOT_SUPPORTED', originalError: event.target && event.target.error } }) } } videoEl.addEventListener('error', onVideoError, true) $(videoEl).on('$destroy', function () { errorAlready = true videoEl.removeEventListener('error', onVideoError) }) } }) }, function (e) { console.log('Download video failed', e, $scope.video) if (e && e.type == 'FS_BROWSER_UNSUPPORTED') { $scope.error = {html: _('error_browser_no_local_file_system_video_md', { 'moz-link': '{1}', 'chrome-link': '{1}', 'telegram-link': '{1}' })} } else { $scope.error = {text: _('error_video_download_failed'), error: e} } }) $scope.$emit('ui_height') $scope.$on('$destroy', function () { downloadPromise.cancel() }) } }) .directive('myLoadGif', function (AppDocsManager, $timeout) { return { link: link, templateUrl: templateUrl('full_gif'), scope: { document: '=' } } function link ($scope, element, attrs) { var imgWrap = $('.img_gif_image_wrap', element) imgWrap.css({width: $scope.document.thumb.width, height: $scope.document.thumb.height}) var downloadPromise = false $scope.isActive = false // Demo // $scope.document.progress = {enabled: true, percent: 30} // $timeout(function () { // $scope.document.progress.percent = 60 // }, 3000) // $timeout(function () { // $scope.document.progress.percent = 100 // }, 10000) $scope.toggle = function (e) { if (e && checkClick(e, true)) { AppDocsManager.saveDocFile($scope.document.id) return false } if ($scope.document.url) { onContentLoaded(function () { $scope.isActive = !$scope.isActive $scope.$emit('ui_height') var video = $('video', element)[0] if (video) { if (!$scope.isActive) { video.pause() video.currentTime = 0 } else { video.play() } } }) return } if (downloadPromise) { downloadPromise.cancel() downloadPromise = false return } downloadPromise = AppDocsManager.downloadDoc($scope.document.id) downloadPromise.then(function () { $timeout(function () { $scope.isActive = true }, 200) }) } // Autoplay small GIFs // if (!Config.Mobile && // $scope.document.size && // $scope.document.size < 1024 * 1024) { // $scope.toggle() // } } }) .directive('myLoadSticker', function (_, MtpApiFileManager, FileManager, AppStickersManager) { var emptySrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' return { link: link, scope: { document: '=' } } function link ($scope, element, attrs) { var imgElement = $('').addClass(attrs.imgClass) var wasAdded = false imgElement.attr('alt', '[' + ($scope.document.stickerEmojiRaw || '') + ' ' + _('conversation_media_sticker') + ']') var dim = (attrs.dim && $scope.$parent.$eval(attrs.dim)) || $scope.document.thumb if (attrs.open && $scope.document.stickerSetInput) { element .addClass('clickable') .on('click', function () { AppStickersManager.openStickerset($scope.document.stickerSetInput) }) } var setSrc = function (blob) { imgElement.attr('src', FileManager.getUrl(blob)) if (!wasAdded) { wasAdded = true imgElement.appendTo(element) } } imgElement.css({ width: dim.width, height: dim.height }) element.css({ width: dim.width, height: dim.height }) var smallLocation = false if ($scope.document.thumb.location) { smallLocation = angular.copy($scope.document.thumb.location) smallLocation.sticker = true } var fullLocation = { _: 'inputDocumentFileLocation', id: $scope.document.id, access_hash: $scope.document.access_hash, dc_id: $scope.document.dc_id, file_name: $scope.document.file_name, version: $scope.document.version, sticker: true } var cachedBlob = MtpApiFileManager.getCachedFile(fullLocation) var fullDone = false if (!cachedBlob) { cachedBlob = MtpApiFileManager.getCachedFile(smallLocation) } else { fullDone = true } if (cachedBlob) { setSrc(cachedBlob) if (fullDone) { return } } else { wasAdded = true imgElement.attr('src', emptySrc).appendTo(element) } if (attrs.thumb && smallLocation) { MtpApiFileManager.downloadSmallFile(smallLocation).then(function (blob) { setSrc(blob) }, function (e) { console.log('Download sticker failed', e, fullLocation) }) } else { MtpApiFileManager.downloadFile($scope.document.dc_id, fullLocation, $scope.document.size).then(function (blob) { setSrc(blob) }, function (e) { console.log('Download sticker failed', e, fullLocation) }) } } }) .directive('myLoadDocument', function (MtpApiFileManager, AppDocsManager, FileManager) { return { link: link, templateUrl: templateUrl('full_document'), scope: { document: '=myLoadDocument' } } function updateModalWidth (element, width) { while (element && !$(element).hasClass('modal-dialog')) { element = element.parentNode } if (element) { $(element).width(width + (Config.Mobile ? 0 : 32)) } } function link ($scope, element, attrs) { var loaderWrap = $('.document_fullsize_with_progress_wrap', element) var fullSizeWrap = $('.document_fullsize_wrap', element) var fullSizeImage = $('.document_fullsize_img', element) var fullWidth = $(window).width() - (Config.Mobile ? 20 : 32) var fullHeight = $(window).height() - 150 if (fullWidth > 800) { fullWidth -= 208 } $scope.imageWidth = fullWidth $scope.imageHeight = fullHeight var thumbPhotoSize = $scope.document.thumb if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') { var wh = calcImageInBox(thumbPhotoSize.width, thumbPhotoSize.height, fullWidth, fullHeight) $scope.imageWidth = wh.w $scope.imageHeight = wh.h var cachedBlob = MtpApiFileManager.getCachedFile(thumbPhotoSize.location) if (cachedBlob) { $scope.thumbSrc = FileManager.getUrl(cachedBlob, 'image/jpeg') } } $scope.frameWidth = Math.max($scope.imageWidth, Math.min(600, fullWidth)) $scope.frameHeight = $scope.imageHeight onContentLoaded(function () { $scope.$emit('ui_height') }) updateModalWidth(element[0], $scope.frameWidth) var checkSizesInt var realImageWidth var realImageHeight AppDocsManager.downloadDoc($scope.document.id).then(function (blob) { var url = FileManager.getUrl(blob, $scope.document.mime_type) var image = new Image() var limit = 100 // 2 sec var checkSizes = function (e) { if ((!image.height || !image.width) && --limit) { return } realImageWidth = image.width realImageHeight = image.height clearInterval(checkSizesInt) var defaultWh = calcImageInBox(image.width, image.height, fullWidth, fullHeight, true) var zoomedWh = {w: realImageWidth, h: realImageHeight} if (defaultWh.w >= zoomedWh.w && defaultWh.h >= zoomedWh.h) { zoomedWh.w *= 4 zoomedWh.h *= 4 } var zoomed = true $scope.toggleZoom = function () { zoomed = !zoomed var imageWidth = (zoomed ? zoomedWh : defaultWh).w var imageHeight = (zoomed ? zoomedWh : defaultWh).h fullSizeImage.css({ width: imageWidth, height: imageHeight, marginTop: $scope.frameHeight > imageHeight ? Math.floor(($scope.frameHeight - imageHeight) / 2) : 0 }) fullSizeWrap.toggleClass('document_fullsize_zoomed', zoomed) } $scope.toggleZoom(false) fullSizeImage.attr('src', url) loaderWrap.hide() fullSizeWrap.css({width: $scope.frameWidth, height: $scope.frameHeight}).show() } checkSizesInt = setInterval(checkSizes, 20) image.onload = checkSizes image.src = url setZeroTimeout(checkSizes) }) } }) .directive('myGeoPointMap', function (ExternalResourcesManager) { return { link: link, scope: { point: '=myGeoPointMap' } } function link ($scope, element, attrs) { var width = element.attr('width') || 200 var height = element.attr('height') || 200 var zoom = width > 200 ? 15 : 13 var useGoogle = false var src if (useGoogle) { var apiKey = Config.ExtCredentials.gmaps.api_key var useApiKey = true src = 'https://maps.googleapis.com/maps/api/staticmap?sensor=false¢er=' + $scope.point['lat'] + ',' + $scope.point['long'] + '&zoom=' + zoom + '&size=' + width + 'x' + height + '&scale=2&markers=color:red|size:big|' + $scope.point['lat'] + ',' + $scope.point['long'] if (useApiKey) { src += '&key=' + apiKey } } else { src = 'https://static-maps.yandex.ru/1.x/?l=map&ll=' + $scope.point['long'] + ',' + $scope.point['lat'] + '&z=' + zoom + '&size=' + width + ',' + height + '&scale=1&pt=' + $scope.point['long'] + ',' + $scope.point['lat'] + ',pm2rdm&lang=en_US' } element.attr('src', 'img/blank.gif') ExternalResourcesManager.downloadByURL(src).then(function (url) { element.attr('src', url.valueOf()) }) } }) .directive('myLoadingDots', function ($interval) { return { link: link } function link ($scope, element, attrs) { element.html(isAnimationSupported(element[0]) ? '
' : '...' ) } var animationSupported function isAnimationSupported (el) { if (animationSupported === undefined) { animationSupported = el.style.animationName !== undefined if (animationSupported === false) { var domPrefixes = 'Webkit Moz O ms Khtml'.split(' ') var i for (i = 0; i < domPrefixes.length; i++) { if (el.style[domPrefixes[i] + 'AnimationName'] !== undefined) { animationSupported = true break } } } } return animationSupported } }) .directive('myFocused', function (shouldFocusOnInteraction) { return { link: function ($scope, element, attrs) { if (!shouldFocusOnInteraction) { return false } setTimeout(function () { setFieldSelection(element[0]) }, 100) } } }) .directive('myFocusOn', function (shouldFocusOnInteraction) { return { link: function ($scope, element, attrs) { $scope.$on(attrs.myFocusOn, function () { if (!shouldFocusOnInteraction) { return false } onContentLoaded(function () { setFieldSelection(element[0]) }) }) } } }) .directive('myFileUpload', function () { return { link: link } function link ($scope, element, attrs) { element.on('change', function () { var self = this $scope.$apply(function () { $scope.photo.file = self.files[0] setTimeout(function () { try { self.value = '' } catch (e) {} }, 1000) }) }) } }) .directive('myModalWidth', function () { return { link: link } function link ($scope, element, attrs) { attrs.$observe('myModalWidth', function (newW) { $(element[0].parentNode.parentNode).css({width: parseInt(newW) + (Config.Mobile ? 0 : 32)}) }) } }) .directive('myModalNav', function () { return { link: link } function link ($scope, element, attrs) { var onKeyDown = function (event) { var target = event.target if (target && (target.tagName == 'INPUT' || target.tagName == 'TEXTAREA')) { return } switch (event.keyCode) { case 39: // right case 32: // space case 34: // pg down case 40: // down $scope.$eval(attrs.next) break case 37: // left case 33: // pg up case 38: // up $scope.$eval(attrs.prev) break } } $(document).on('keydown', onKeyDown) $scope.$on('$destroy', function () { $(document).off('keydown', onKeyDown) }) } }) .directive('myCustomBackground', function () { return { link: link } function link ($scope, element, attrs) { $('html').css({background: attrs.myCustomBackground}) $scope.$on('$destroy', function () { $('html').css({background: ''}) }) } }) .directive('myInfiniteScroller', function () { return { link: link, scope: true } function link ($scope, element, attrs) { var scrollableWrap = $('.nano-content', element)[0] var moreNotified = false $(scrollableWrap).on('scroll', function (e) { if (!element.is(':visible')) return if (!moreNotified && scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) { moreNotified = true $scope.$apply(function () { $scope.slice.limit += ($scope.slice.limitDelta || 20) }) onContentLoaded(function () { moreNotified = false $(element).nanoScroller() }) } }) } }) .directive('myModalPosition', function ($window, $timeout) { return { link: link } function link ($scope, element, attrs) { var updateMargin = function () { if (Config.Mobile && $(element[0].parentNode.parentNode.parentNode).hasClass('mobile_modal')) { return } var height = element[0].parentNode.offsetHeight var modal = element[0].parentNode.parentNode.parentNode var bottomPanel = $('.media_modal_bottom_panel_wrap', modal)[0] var contHeight = modal.offsetHeight - ((bottomPanel && bottomPanel.offsetHeight) || 0) if (height < contHeight) { $(element[0].parentNode).css('marginTop', (contHeight - height) / 2) } else { $(element[0].parentNode).css('marginTop', '') } if (attrs.animation != 'no') { $timeout(function () { $(element[0].parentNode).addClass('modal-content-animated') }, 300) } } onContentLoaded(updateMargin) $($window).on('resize', updateMargin) $scope.$on('ui_height', function (e, sync) { if (sync) { updateMargin() } else { onContentLoaded(updateMargin) } }) } }) .directive('myVerticalPosition', function ($window, $timeout) { return { link: link } function link ($scope, element, attrs) { var usePadding = attrs.padding === 'true' var prevMargin = 0 var updateMargin = function () { var height = element[0].offsetHeight var fullHeight = height - (height && usePadding ? 2 * prevMargin : 0) var ratio = (attrs.myVerticalPosition && parseFloat(attrs.myVerticalPosition)) || 0.5 var contHeight = attrs.contHeight ? $scope.$eval(attrs.contHeight) : $($window).height() var margin = fullHeight < contHeight ? parseInt((contHeight - fullHeight) * ratio) : '' var styles = usePadding ? {paddingTop: margin, paddingBottom: margin} : {marginTop: margin, marginBottom: margin} element.css(styles) element.addClass('vertical-aligned') if (prevMargin !== margin) { $scope.$emit('ui_height') } prevMargin = margin } $($window).on('resize', updateMargin) onContentLoaded(updateMargin) $scope.$on('ui_height', function () { onContentLoaded(updateMargin) }) } }) .directive('myUserStatus', function ($filter, $rootScope, AppUsersManager) { var statusFilter = $filter('userStatus') var ind = 0 var statuses = {} setInterval(updateAll, 90000) $rootScope.$on('stateSynchronized', function () { setTimeout(function () { updateAll() }, 100) }) return { link: link } function updateAll () { angular.forEach(statuses, function (update) { update() }) } function link ($scope, element, attrs) { var userID var curInd = ind++ var update = function () { var user = AppUsersManager.getUser(userID) element .html(statusFilter(user, attrs.botChatPrivacy)) .toggleClass('status_online', (user.status && user.status._ == 'userStatusOnline') || false) // console.log(dT(), 'update status', element[0], user.status && user.status, tsNow(true), element.html()) } $scope.$watch(attrs.myUserStatus, function (newUserID) { userID = newUserID update() }) $scope.$on('user_update', function (e, updUserID) { if (userID == updUserID) { update() } }) statuses[curInd] = update $scope.$on('$destroy', function () { delete statuses[curInd] }) } }) .directive('myChatStatus', function ($rootScope, _, MtpApiManager, AppChatsManager, AppUsersManager, AppProfileManager) { var ind = 0 var statuses = {} var allPluralize = _.pluralize('group_modal_pluralize_participants') var onlinePluralize = _.pluralize('group_modal_pluralize_online_participants') var myID = 0 MtpApiManager.getUserID().then(function (newMyID) { myID = newMyID }) setInterval(updateAll, 90000) return { link: link } function updateAll () { angular.forEach(statuses, function (update) { update() }) } function link ($scope, element, attrs) { var chatID var curInd = ind++ var jump = 0 var participantsCount = 0 var participants = {} var updateParticipants = function () { var curJump = ++jump participantsCount = 0 participants = {} if (!chatID) { update() return } AppProfileManager.getChatFull(chatID).then(function (chatFull) { if (curJump != jump) { return } var participantsVector = (chatFull.participants || {}).participants || [] participantsCount = participantsVector.length angular.forEach(participantsVector, function (participant) { participants[participant.user_id] = true }) if (chatFull.participants_count) { participantsCount = chatFull.participants_count || 0 } update() }) } var update = function () { var html = allPluralize(participantsCount) var onlineCount = 0 if (!AppChatsManager.isChannel(chatID)) { var wasMe = false angular.forEach(participants, function (t, userID) { var user = AppUsersManager.getUser(userID) if (user.status && user.status._ == 'userStatusOnline') { if (user.id == myID) { wasMe = true } onlineCount++ } }) if (onlineCount > 1 || onlineCount == 1 && !wasMe) { html = _('group_modal_participants', {total: html, online: onlinePluralize(onlineCount)}) } } if (!onlineCount && !participantsCount) { html = '' } element.html(html) } $scope.$watch(attrs.myChatStatus, function (newChatID) { chatID = newChatID updateParticipants() }) $rootScope.$on('chat_full_update', function (e, updChatID) { if (chatID == updChatID) { updateParticipants() } }) $rootScope.$on('user_update', function (e, updUserID) { if (participants[updUserID]) { update() } }) statuses[curInd] = update $scope.$on('$destroy', function () { delete statuses[curInd] }) } }) .directive('myPeerMuted', function ($rootScope, NotificationsManager) { return { link: link } function link ($scope, element, attrs) { var peerID = $scope.$eval(attrs.myPeerMuted) var className = attrs.mutedClass || 'muted' var unsubscribe = $rootScope.$on('notify_settings', function (e, data) { if (data.peerID == peerID) { updateClass(peerID, element, className) } }) updateClass(peerID, element, className) $scope.$on('$destroy', unsubscribe) } function updateClass (peerID, element, className) { NotificationsManager.getPeerMuted(peerID).then(function (muted) { element.toggleClass(className, muted) }) } }) .directive('myPeerLink', function (AppChatsManager, AppUsersManager) { return { link: link } function link ($scope, element, attrs) { var override = attrs.userOverride && $scope.$eval(attrs.userOverride) || {} var short = attrs.short && $scope.$eval(attrs.short) var username = attrs.username && $scope.$eval(attrs.username) var peerID var update = function () { if (element[0].className.indexOf('user_color_') != -1) { element[0].className = element[0].className.replace(/user_color_\d+/g, '') } if (peerID > 0) { var user = AppUsersManager.getUser(peerID) var prefix = username ? '@' : '' var key = username ? 'username' : (short ? 'rFirstName' : 'rFullName') element.html( prefix + (override[key] || user[key] || '').valueOf() + (attrs.verified && user.pFlags && user.pFlags.verified ? ' ' : '') ) if (attrs.color && $scope.$eval(attrs.color)) { element.addClass('user_color_' + user.num) } } else { var chat = AppChatsManager.getChat(-peerID) element.html( (chat.rTitle || '').valueOf() + (attrs.verified && chat.pFlags && chat.pFlags.verified ? ' ' : '') ) } } if (element[0].tagName == 'A' && !hasOnclick(element[0])) { element.on('click', function () { if (peerID > 0) { AppUsersManager.openUser(peerID, override) } else { AppChatsManager.openChat(-peerID) } }) } if (attrs.peerWatch) { // userWatch, chatWatch $scope.$watch(attrs.myPeerLink, function (newPeerID) { peerID = newPeerID update() }) } else { peerID = $scope.$eval(attrs.myPeerLink) update() } if (!attrs.noWatch) { $scope.$on('user_update', function (e, updUserID) { if (peerID == updUserID) { update() } }) $scope.$on('chat_update', function (e, updChatID) { if (peerID == -updChatID) { update() } }) } } }) .directive('myPeerPhotolink', function (AppPeersManager, AppUsersManager, AppChatsManager, MtpApiFileManager, FileManager) { return { link: link } function link ($scope, element, attrs) { element.addClass('peer_photo_init') var peerID, peer var peerPhoto var imgEl = $('') var initEl = $('') var jump = 0 var prevClass = false var setPeerID = function (newPeerID) { if (peerID == newPeerID) { return false } peerID = newPeerID peer = AppPeersManager.getPeer(peerID) var newClass = 'user_bgcolor_' + (peer.num || 1) if (newClass != prevClass) { if (prevClass) { initEl.removeClass(prevClass) } initEl.addClass(newClass) prevClass = newClass } updatePeerPhoto() return true } var updatePeerPhoto = function () { var curJump = ++jump peerPhoto = peer.photo && angular.copy(peer.photo.photo_small) var hasPhoto = peerPhoto !== undefined if (hasPhoto) { var cachedBlob = MtpApiFileManager.getCachedFile(peer.photo.photo_small) if (cachedBlob) { initEl.remove() imgEl.prependTo(element).attr('src', FileManager.getUrl(cachedBlob, 'image/jpeg')) return } } initEl.attr('data-content', peer.initials || '').prependTo(element) imgEl.remove() if (hasPhoto) { MtpApiFileManager.downloadSmallFile(peer.photo.photo_small).then(function (blob) { if (curJump != jump) { return } initEl.remove() imgEl.prependTo(element).attr('src', FileManager.getUrl(blob, 'image/jpeg')) }, function (e) { console.log('Download image failed', e, peer.photo.photo_small, element[0]) }) } } if (element[0].tagName == 'A' && !attrs.noOpen) { element.on('click', function (e) { if (peerID > 0) { AppUsersManager.openUser(peerID, attrs.userOverride && $scope.$eval(attrs.userOverride)) } else { AppChatsManager.openChat(-peerID) } }) } $scope.$watch(attrs.myPeerPhotolink, setPeerID) setPeerID($scope.$eval(attrs.myPeerPhotolink)) if (attrs.watch) { $scope.$on('user_update', function (e, updUserID) { if (peerID == updUserID) { peer = AppPeersManager.getPeer(peerID) if (!angular.equals(peer.photo && peer.photo.photo_small, peerPhoto) || !peerPhoto) { updatePeerPhoto() } } }) $scope.$on('chat_update', function (e, updChatID) { if (peerID == -updChatID) { if (!angular.equals(peer.photo && peer.photo.photo_small, peerPhoto)) { updatePeerPhoto() } } }) } } }) .directive('myAudioPlayer', function ($timeout, $q, Storage, AppDocsManager, AppMessagesManager, ErrorService) { var currentPlayer = false var audioVolume = 0.5 Storage.get('audio_volume').then(function (newAudioVolume) { if (newAudioVolume > 0 && newAudioVolume <= 1.0) { audioVolume = newAudioVolume } }) var onAudioError = function (event) { if (!event.target || !event.target.error || event.target.error.code == event.target.error.MEDIA_ERR_DECODE || event.target.error.code == event.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED) { ErrorService.show({ error: { type: 'MEDIA_TYPE_NOT_SUPPORTED', originalError: event.target && event.target.error } }) } } return { link: link, scope: { audio: '=', message: '=' }, templateUrl: templateUrl('audio_player') } function checkPlayer (newPlayer) { if (newPlayer === currentPlayer) { return false } if (currentPlayer) { currentPlayer.pause() } currentPlayer = newPlayer } function link ($scope, element, attrs) { AppDocsManager.updateDocDownloaded($scope.audio.id) $scope.volume = audioVolume $scope.mediaPlayer = {} $scope.download = function () { AppDocsManager.saveDocFile($scope.audio.id) } $scope.togglePlay = function () { if ($scope.audio.url) { checkPlayer($scope.mediaPlayer.player) $scope.mediaPlayer.player.playPause() } else if ($scope.audio.progress && $scope.audio.progress.enabled) { } else { AppDocsManager.downloadDoc($scope.audio.id).then(function () { onContentLoaded(function () { var errorListenerEl = $('audio', element)[0] || element[0] if (errorListenerEl) { var errorAlready = false var onAudioError = function (event) { if (errorAlready) { return } if (!event.target || !event.target.error || event.target.error.code == event.target.error.MEDIA_ERR_DECODE || event.target.error.code == event.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED) { errorAlready = true ErrorService.show({ error: { type: 'MEDIA_TYPE_NOT_SUPPORTED', originalError: event.target && event.target.error } }) } } errorListenerEl.addEventListener('error', onAudioError, true) $scope.$on('$destroy', function () { errorAlready = true errorListenerEl.removeEventListener('error', onAudioError) }) } setTimeout(function () { checkPlayer($scope.mediaPlayer.player) $scope.mediaPlayer.player.setVolume(audioVolume) $scope.mediaPlayer.player.play() if ($scope.message && !$scope.message.pFlags.out && $scope.message.pFlags.media_unread) { AppMessagesManager.readMessages([$scope.message.mid]) } }, 300) }) }) } } $scope.seek = function (position) { if ($scope.mediaPlayer && $scope.mediaPlayer.player) { $scope.mediaPlayer.player.seek(position) } else { $scope.togglePlay() } } $scope.setVolume = function (volume) { audioVolume = volume Storage.set({audio_volume: volume}) if ($scope.mediaPlayer && $scope.mediaPlayer.player) { $scope.mediaPlayer.player.setVolume(volume) } } } }) .directive('mySlider', function ($window) { return { link: link, templateUrl: templateUrl('slider') } function link ($scope, element, attrs) { var wrap = $('.tg_slider_wrap', element) var fill = $('.tg_slider_track_fill', element) var thumb = $('.tg_slider_thumb', element) var width = wrap.width() var thumbWidth = Math.ceil(thumb.width()) var model = attrs.sliderModel var sliderCallback = attrs.sliderOnchange var minValue = 0.0 var maxValue = 1.0 var lastUpdValue = false var lastMinPageX = false if (attrs.sliderMin) { $scope.$watch(attrs.sliderMin, function (newMinValue) { minValue = newMinValue || 0.0 }) } if (attrs.sliderMax) { $scope.$watch(attrs.sliderMax, function (newMaxValue) { maxValue = newMaxValue || 1.0 }) } var onSliderMove = function (e) { e = e.originalEvent || e var offsetX = (e.touches && e.touches[0] ? e.touches[0].pageX : e.pageX) - lastMinPageX offsetX = Math.min(width, Math.max(0, offsetX)) // console.log(e.type, lastMinPageX, e.pageX, offsetX) lastUpdValue = minValue + offsetX / width * (maxValue - minValue) if (sliderCallback) { $scope.$eval(sliderCallback, {value: lastUpdValue}) } else { $scope.$eval(model + '=' + lastUpdValue) } thumb.css('left', Math.max(0, offsetX - thumbWidth)) fill.css('width', offsetX) return cancelEvent(e) } var stopSliderTrack = function () { $($window).off('mousemove touchmove', onSliderMove) $($window).off('mouseup touchend touchcancel touchleave', stopSliderTrack) } $scope.$watch(model, function (newVal) { if (newVal != lastUpdValue && newVal !== undefined) { var percent = Math.max(0, (newVal - minValue) / (maxValue - minValue)) if (width) { var offsetX = Math.ceil(width * percent) offsetX = Math.min(width, Math.max(0, offsetX)) thumb.css('left', Math.max(0, offsetX - thumbWidth)) fill.css('width', offsetX) } else { thumb.css('left', percent * 100 + '%') fill.css('width', percent * 100 + '%') } lastUpdValue = false } }) element.on('dragstart selectstart', cancelEvent) element.on('mousedown touchstart', function (e) { if (!width) { width = wrap.width() if (!width) { console.error('empty width') return cancelEvent(e) } } stopSliderTrack() e = e.originalEvent || e var offsetX if (e.touches && e.touches[0]) { lastMinPageX = element.position().left offsetX = e.touches[0].pageX - lastMinPageX } else if (e.offsetX !== undefined) { offsetX = e.offsetX lastMinPageX = e.pageX - offsetX } else if (e.layerX !== undefined) { offsetX = e.layerX lastMinPageX = e.pageX - offsetX } else { return cancelEvent(e) } // console.log(e.type, e, lastMinPageX, e.pageX, offsetX) lastUpdValue = minValue + offsetX / width * (maxValue - minValue) if (sliderCallback) { $scope.$eval(sliderCallback, {value: lastUpdValue}) } else { $scope.$eval(model + '=' + lastUpdValue) } thumb.css('left', Math.max(0, offsetX - thumbWidth)) fill.css('width', offsetX) $($window).on('mousemove touchmove', onSliderMove) $($window).on('mouseup touchend touchcancel touchleave', stopSliderTrack) return cancelEvent(e) }) } }) .directive('myLabeledInput', function () { return { link: link } function link ($scope, element, attrs) { var input = $('.md-input:first', element) var label = $('.md-input-label:first', element) var isDisabled = input[0] && input[0].tagName == 'SPAN' var focused = false var updateHasValueClass = function () { if (isDisabled) { element.toggleClass('md-input-has-value', input.html().length > 0) } else { element.toggleClass('md-input-has-value', focused || input.val().length > 0) } } updateHasValueClass() onContentLoaded(function () { updateHasValueClass() setZeroTimeout(function () { element.addClass('md-input-animated') }) }) if (!isDisabled) { input.on('blur focus change', function (e) { focused = e.type == 'focus' element.toggleClass('md-input-focused', focused) updateHasValueClass() }) } $scope.$on('value_updated', function () { setZeroTimeout(function () { updateHasValueClass() }) }) } }) .directive('myCopyField', function (toaster, _) { return { scope: { selectEvent: '=myCopyField' }, link: link } function link ($scope, element, attrs) { element.attr('readonly', 'true') element[0].readonly = true element.on('click', function () { this.select() }) if ($scope.selectEvent) { $scope.$on($scope.selectEvent, function () { setTimeout(function () { element[0].focus() element[0].select() }, 100) }) } } }) .directive('myCopyLink', function ($compile, $timeout, _) { return { restrict: 'A', replace: false, terminal: true, priority: 1000, link: link } function link ($scope, element, attrs) { element.attr('tooltip', '{{ttLabel}}') element.removeAttr('my-copy-link') element.removeAttr('data-my-copy-link') var resetPromise = false var resetTooltip = function () { $timeout.cancel(resetPromise) resetPromise = false $scope.ttLabel = _('conversations_modal_share_url_copy_raw') } resetTooltip() $compile(element)($scope) var clipboard = new Clipboard(element[0]) clipboard.on('success', function (e) { $timeout.cancel(resetPromise) $scope.$apply(function () { $scope.ttLabel = _('clipboard_copied_raw') }) resetPromise = $timeout(resetTooltip, 2000) }) clipboard.on('error', function (e) { $timeout.cancel(resetPromise) var langKey = Config.Navigator.osX ? 'clipboard_press_cmd_c' : 'clipboard_press_ctrl_c' $scope.$apply(function () { $scope.ttLabel = _(langKey + '_raw') }) resetPromise = $timeout(resetTooltip, 5000) }) $scope.$on('$destroy', function () { clipboard.destroy() }) } }) .directive('mySubmitOnEnter', function () { return { link: link } function link ($scope, element, attrs) { element.on('keydown', function (event) { if (event.keyCode == 13) { element.trigger('submit') return cancelEvent(event) } }) } }) .directive('myArcProgress', function () { var html = '\ \ \ \ \ \ \ \ \ ' function updateProgress (bar, progress, fullLen) { progress = Math.max(0.0, Math.min(progress, 1.0)) var minProgress = 0.2 progress = minProgress + (1 - minProgress) * progress bar.css({strokeDasharray: (progress * fullLen) + ', ' + ((1 - progress) * fullLen)}) } var num = 0 return { scope: { progress: '=myArcProgress' }, link: function ($scope, element, attrs) { var intermediate = !attrs.myArcProgress var width = attrs.width || element.width() || 40 var stroke = attrs.stroke || (width / 2 * 0.14) var center = width / 2 var radius = center - (stroke / 2) // Doesn't work without unique id for every gradient var curNum = ++num element .html(html.replace('%id%', curNum)) .addClass('progress-arc-wrap') .addClass(intermediate ? 'progress-arc-intermediate' : 'progress-arc-percent') .css({width: width, height: width}) $(element[0].firstChild) .attr('width', width) .attr('height', width) var bar = $('.progress-arc-bar', element) bar .attr('cx', center) .attr('cy', center) .attr('r', radius) .css({strokeWidth: stroke}) var fullLen = 2 * Math.PI * radius if (intermediate) { updateProgress(bar, 0.3, fullLen) bar.css({stroke: 'url(#grad_intermediate' + curNum + ')'}) } else { $scope.$watch('progress', function (newProgress) { updateProgress(bar, newProgress / 100.0, fullLen) }) } } } }) .directive('myScrollToOn', function () { return { link: function ($scope, element, attrs) { var ev = attrs.myScrollToOn var doScroll = function () { onContentLoaded(function () { $('html, body').animate({ scrollTop: element.offset().top }, 200) }) } if (ev == '$init') { doScroll() } else { $scope.$on(ev, doScroll) } } } }) .directive('myComposerDropdown', function () { return { templateUrl: templateUrl('composer_dropdown') } }) .directive('myEmojiSuggestions', function () { return { link: function ($scope, element, attrs) { $scope.$watchCollection('emojiCodes', function (codes) { // var codes = $scope.$eval(attrs.myEmojiSuggestions) var html = [] var iconSize = Config.Mobile ? 26 : 20 var emoticonCode, emoticonData var spritesheet, pos var 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('
  • :' + encodeEntities(emoticonData[1][0]) + ':
  • ') } } // onContentLoaded(function () { element.html(html.join('')) console.log(dT(), 'emoji done') // }) }) } } }) .directive('myInlineResults', function (AppPhotosManager, ExternalResourcesManager, AppDocsManager) { return { templateUrl: templateUrl('inline_results'), scope: { botResults: '=myInlineResults' }, link: function ($scope, element, attrs) { $scope.$watch('botResults.results', function (results) { angular.forEach(results, function (result) { if (result.thumb_url && !result.thumbUrl) { ExternalResourcesManager.downloadByURL(result.thumb_url).then(function (url) { result.thumbUrl = url }) } if (result.type == 'gif' && result.content_url && !result.contentUrl) { ExternalResourcesManager.downloadByURL(result.content_url).then(function (url) { result.contentUrl = url }) } if ((result.type == 'gif' || result.type == 'sticker') && result.document) { AppDocsManager.downloadDoc(result.document.id) } var photoSize if (result.type == 'photo' && result.photo) { photoSize = AppPhotosManager.choosePhotoSize(result.photo, result.thumbW, result.thumbH) var dim = calcImageInBox(photoSize.w, photoSize.h, result.thumbW, result.thumbH) result.thumb = { width: dim.w, height: dim.h, location: photoSize.location, size: photoSize.size } } if (result.type == 'game' && result.photo) { photoSize = AppPhotosManager.choosePhotoSize(result.photo, 100, 100) // var dim = calcImageInBox(photoSize.w, photoSize.h, result.thumbW, result.thumbH) result.thumb = { // width: dim.w, // height: dim.h, location: photoSize.location, size: photoSize.size } } }) }) } } }) .directive('myGameCommunication', function ($window) { function link ($scope, element, attrs) { onContentLoaded(function () { var iframe = $('iframe, webview', element)[0] var contentWindow = iframe.contentWindow var handler = function (event) { event = event.originalEvent || event if (event.source && event.source != contentWindow) { return } var data = event.data try { var dataParsed = JSON.parse(data) } catch (e) { return } if (!dataParsed || !dataParsed.eventType) { return } $scope.$emit('game_frame_event', dataParsed) } $($window).on('message', handler) $scope.$on('$destroy', function () { $($window).off('message', handler) }) }) } return { link: link } }) .directive('myEmojiImage', function (RichTextProcessor) { function link ($scope, element, attrs) { var emoji = attrs.myEmojiImage var html = RichTextProcessor.wrapRichText(emoji, {noLinks: true, noLinebreaks: true}) element.html(html.valueOf()) } return { link: link } }) .directive('myExternalEmbed', function () { var twitterAttached = false var facebookAttached = false var gplusAttached = false var twitterPendingWidgets = [] var facebookPendingWidgets = [] var embedTag = Config.Modes.chrome_packed ? 'webview' : 'iframe' function link ($scope, element, attrs) { var embedData = $scope.$eval(attrs.myExternalEmbed) if (!embedData) { return } var html = '' var callback = false var needTwitter = false var videoID switch (embedData[0]) { case 'youtube': videoID = embedData[1] html = '
    <' + embedTag + ' type="text/html" frameborder="0" ' + 'src="https://www.youtube.com/embed/' + videoID + '?autoplay=0&controls=2" webkitallowfullscreen mozallowfullscreen allowfullscreen>
    ' break case 'vimeo': videoID = embedData[1] html = '
    <' + embedTag + ' type="text/html" frameborder="0" ' + 'src="https://player.vimeo.com/video/' + videoID + '?title=0&byline=0&portrait=0" webkitallowfullscreen mozallowfullscreen allowfullscreen>
    ' break case 'instagram': var instaID = embedData[1] html = '
    <' + embedTag + ' type="text/html" frameborder="0" ' + 'src="https://instagram.com/p/' + instaID + '/embed/">
    ' break case 'vine': var vineID = embedData[1] html = '
    <' + embedTag + ' type="text/html" frameborder="0" ' + 'src="https://vine.co/v/' + vineID + '/embed/simple">
    ' break case 'soundcloud': var soundcloudUrl = embedData[1] html = '
    <' + embedTag + ' type="text/html" frameborder="0" ' + 'src="https://w.soundcloud.com/player/?url=' + encodeEntities(encodeURIComponent(soundcloudUrl)) + '&auto_play=false&hide_related=true&show_comments=false&show_user=true&show_reposts=false&visual=true">
    ' break case 'spotify': var spotifyUrl = embedData[1] html = '
    <' + embedTag + ' type="text/html" frameborder="0" allowtransparency="true" ' + 'src="https://embed.spotify.com/?uri=spotify:' + encodeEntities(encodeURIComponent(spotifyUrl)) + '">
    ' break case 'twitter': html = '
    ' callback = function () { if (!twitterAttached) { twitterAttached = true $('