diff --git a/app/img/Telegram.svg b/app/img/Telegram.svg new file mode 100644 index 00000000..314eb135 --- /dev/null +++ b/app/img/Telegram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/js/app.js b/app/js/app.js index ef8a942f..6f90ca53 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -7,6 +7,12 @@ 'use strict'; +var extraModules = []; +if (Config.Modes.animations) { + extraModules.push('ngAnimate'); +} + + // Declare app level module which depends on filters, and services angular.module('myApp', [ 'ngRoute', @@ -24,7 +30,7 @@ angular.module('myApp', [ PRODUCTION_ONLY_END*/ 'myApp.directives', 'myApp.controllers' -]). +].concat(extraModules)). config(['$locationProvider', '$routeProvider', '$compileProvider', 'StorageProvider', function($locationProvider, $routeProvider, $compileProvider, StorageProvider) { $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|blob|filesystem|chrome-extension|app):|data:image\//); diff --git a/app/js/controllers.js b/app/js/controllers.js index 24df3fde..55b2d5f4 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -1058,6 +1058,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.selectedReply = selectedReply; $scope.selectedCancel = selectedCancel; $scope.selectedFlush = selectedFlush; + $scope.selectInlineBot = selectInlineBot; $scope.startBot = startBot; $scope.cancelBot = cancelBot; @@ -1195,7 +1196,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.historyState.typing.splice(0, $scope.historyState.typing.length); $scope.$broadcast('ui_peer_change'); $scope.$broadcast('ui_history_change'); - safeReplaceObject($scope.state, {loaded: true, empty: !peerHistory.messages.length}); + safeReplaceObject($scope.state, {loaded: true, empty: !peerHistory.messages.length, mayBeHasMore: true}); updateBotActions(); updateChannelActions(); @@ -1299,14 +1300,14 @@ angular.module('myApp.controllers', ['myApp.i18n']) return; } lessPending = false; - lessActive = true; + $scope.state.lessActive = lessActive = true; var curJump = jump, curLessJump = ++lessJump, limit = 0, backLimit = 20; AppMessagesManager.getHistory($scope.curDialog.peerID, minID, limit, backLimit).then(function (historyResult) { - lessActive = false; + $scope.state.lessActive = lessActive = false; if (curJump != jump || curLessJump != lessJump) return; var i, id; @@ -1347,7 +1348,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) return; } morePending = false; - moreActive = true; + $scope.state.moreActive = moreActive = true; var curJump = jump, curMoreJump = ++moreJump, @@ -1358,7 +1359,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) : AppMessagesManager.getHistory($scope.curDialog.peerID, maxID, limit); getMessagesPromise.then(function (historyResult) { - moreActive = false; + $scope.state.moreActive = moreActive = false; if (curJump != jump || curMoreJump != moreJump) return; angular.forEach(historyResult.history, function (id) { @@ -1405,9 +1406,9 @@ angular.module('myApp.controllers', ['myApp.i18n']) limit = 10; } - moreActive = false; + $scope.state.moreActive = moreActive = false; morePending = false; - lessActive = false; + $scope.state.lessActive = lessActive = false; lessPending = false; var prerenderedLen = peerHistory.messages.length; @@ -1550,23 +1551,31 @@ angular.module('myApp.controllers', ['myApp.i18n']) var target = $event.target; while (target) { - if (target.className.indexOf('im_message_outer_wrap') != -1) { + if (target instanceof SVGElement) { + target = target.parentNode; + continue; + } + if (target.className && target.className.indexOf('im_message_outer_wrap') != -1) { if (Config.Mobile) { return false; } break; } + if (target.className && + target.className.indexOf('im_message_date') != -1) { + if ($scope.historyState.canReply) { + selectedReply(messageID); + } else { + selectedForward(messageID); + } + return false; + } if (Config.Mobile && + target.className && target.className.indexOf('im_message_body') != -1) { break; } - if (target.tagName == 'A' || - target.onclick || - target.getAttribute('ng-click')) { - return false; - } - var events = $._data(target, 'events'); - if (events && (events.click || events.mousedown)) { + if (target.tagName == 'A' || hasOnlick(target)) { return false; } target = target.parentNode; @@ -1651,6 +1660,11 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.$broadcast('messages_select'); } + function selectInlineBot (botID, $event) { + $scope.$broadcast('inline_bot_select', botID); + return cancelEvent($event); + } + function selectedCancel (noBroadcast) { $scope.selectedMsgs = {}; $scope.selectedCount = 0; @@ -2069,7 +2083,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.$on('user_update', angular.noop); }) - .controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, MtpApiFileManager, RichTextProcessor) { + .controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, AppInlineBotsManager, MtpApiFileManager, RichTextProcessor) { $scope.$watch('curDialog.peer', resetDraft); $scope.$on('user_update', angular.noop); @@ -2091,6 +2105,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.$watch('draftMessage.files', onFilesSelected); $scope.$watch('draftMessage.sticker', onStickerSelected); $scope.$watch('draftMessage.command', onCommandSelected); + $scope.$watch('draftMessage.inlineResultID', onInlineResultSelected); $scope.$on('history_reply_markup', function (e, peerData) { if (peerData.peerID == $scope.curDialog.peerID) { @@ -2098,6 +2113,12 @@ angular.module('myApp.controllers', ['myApp.i18n']) } }); + $scope.$on('inline_bot_select', function (e, botID) { + var bot = AppUsersManager.getUser(botID); + $scope.draftMessage.text = '@' + bot.username + ' ';; + $scope.$broadcast('ui_peer_draft', {focus: true}); + }); + $scope.replyKeyboardToggle = replyKeyboardToggle; $scope.toggleSlash = toggleSlash; @@ -2250,6 +2271,9 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.draftMessage.text = ''; $scope.$broadcast('ui_peer_draft'); } + + delete $scope.draftMessage.inlineProgress; + $scope.$broadcast('inline_results', false); } function applyDraftAttachment (e, attachment) { @@ -2394,6 +2418,60 @@ angular.module('myApp.controllers', ['myApp.i18n']) Storage.remove('draft' + $scope.curDialog.peerID); // console.log(dT(), 'draft delete', 'draft' + $scope.curDialog.peerID); } + checkInlinePattern(newVal); + } + + var inlineUsernameRegex = /^@([a-zA-Z\d_]{1,32})( | )([\s\S]*)$/; + var getInlineResultsTO = false; + var jump = 0; + + function checkInlinePattern (message) { + if (getInlineResultsTO) { + $timeout.cancel(getInlineResultsTO); + } + var curJump = ++jump; + if (!message || !message.length) { + delete $scope.draftMessage.inlineProgress; + $scope.$broadcast('inline_results', false); + return; + } + var matches = message.match(inlineUsernameRegex); + if (!matches) { + delete $scope.draftMessage.inlineProgress; + $scope.$broadcast('inline_results', false); + return; + } + var username = matches[1]; + $scope.draftMessage.inlineProgress = true; + AppPeersManager.resolveInlineMention(username).then(function (inlineBot) { + if (curJump != jump) { + return; + } + $scope.$broadcast('inline_placeholder', { + prefix: '@' + username + matches[2], + placeholder: inlineBot.placeholder + }); + if (getInlineResultsTO) { + $timeout.cancel(getInlineResultsTO); + } + getInlineResultsTO = $timeout(function () { + AppInlineBotsManager.getInlineResults(inlineBot.id, matches[3], '').then(function (botResults) { + getInlineResultsTO = false; + if (curJump != jump) { + return; + } + botResults.text = message; + $scope.$broadcast('inline_results', botResults); + delete $scope.draftMessage.inlineProgress; + }, function () { + $scope.$broadcast('inline_results', false); + delete $scope.draftMessage.inlineProgress; + }); + }, 500); + }, function () { + $scope.$broadcast('inline_results', false); + delete $scope.draftMessage.inlineProgress; + }); } function onTyping () { @@ -2455,7 +2533,6 @@ angular.module('myApp.controllers', ['myApp.i18n']) fwdsSend(); } delete $scope.draftMessage.sticker; - resetDraft(); } function onCommandSelected (command) { @@ -2467,6 +2544,25 @@ angular.module('myApp.controllers', ['myApp.i18n']) delete $scope.draftMessage.sticker; delete $scope.draftMessage.text; delete $scope.draftMessage.command; + delete $scope.draftMessage.inlineResultID; + $scope.$broadcast('ui_message_send'); + $scope.$broadcast('ui_peer_draft'); + } + + function onInlineResultSelected (qID) { + if (!qID) { + return; + } + var options = { + replyToMsgID: $scope.draftMessage.replyToMessage && $scope.draftMessage.replyToMessage.mid + }; + AppInlineBotsManager.sendInlineResult($scope.curDialog.peerID, qID, options); + fwdsSend(); + resetDraft(); + delete $scope.draftMessage.sticker; + delete $scope.draftMessage.text; + delete $scope.draftMessage.command; + delete $scope.draftMessage.inlineResultID; $scope.$broadcast('ui_message_send'); $scope.$broadcast('ui_peer_draft'); } @@ -4654,11 +4750,14 @@ angular.module('myApp.controllers', ['myApp.i18n']) .controller('StickersetModalController', function ($scope, $rootScope, $modalInstance, MtpApiManager, RichTextProcessor, AppStickersManager, AppDocsManager, AppMessagesManager, LocationParamsService) { $scope.slice = {limit: 20, limitDelta: 20}; + var fullSet; + AppStickersManager.getStickerset($scope.inputStickerset).then(function (result) { $scope.$broadcast('ui_height'); $scope.stickersetLoaded = true; + fullSet = result; $scope.stickerset = result.set; - $scope.stickersetInstalled = result.installed; + $scope.stickersetInstalled = result.set.pFlags.installed == true; $scope.documents = result.documents; $scope.stickerEmojis = {}; @@ -4673,7 +4772,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) }); $scope.toggleInstalled = function (installed) { - AppStickersManager.installStickerset($scope.stickerset, !installed).then(function () { + AppStickersManager.installStickerset(fullSet, !installed).then(function () { $scope.stickersetInstalled = installed; }) }; diff --git a/app/js/directives.js b/app/js/directives.js index da7388f0..d0082512 100755 --- a/app/js/directives.js +++ b/app/js/directives.js @@ -1099,6 +1099,7 @@ angular.module('myApp.directives', ['myApp.filters']) historyEl = $('.im_history', element)[0], scrollableWrap = $('.im_history_scrollable_wrap', element)[0], scrollable = $('.im_history_scrollable', element)[0], + emptyWrapEl = $('.im_history_empty_wrap', element)[0], bottomPanelWrap = $('.im_bottom_panel_wrap', element)[0], sendFormWrap = $('.im_send_form_wrap', element)[0], headWrap = $('.tg_page_head')[0], @@ -1412,7 +1413,7 @@ angular.module('myApp.directives', ['myApp.filters']) $(historyMessagesEl).css({marginTop: 0}); var marginTop = scrollableWrap.offsetHeight - historyMessagesEl.offsetHeight - - 20 + - emptyWrapEl.offsetHeight - (Config.Mobile ? 0 : 39); if (historyMessagesEl.offsetHeight > 0 && marginTop > 0) { @@ -1429,8 +1430,7 @@ angular.module('myApp.directives', ['myApp.filters']) }) - .directive('mySendForm', function (_, $window, $compile, $modalStack, $http, $interpolate, Storage, AppStickersManager, AppDocsManager, ErrorService, shouldFocusOnInteraction) { - + .directive('mySendForm', function (_, $timeout, $compile, $modalStack, $http, $interpolate, Storage, AppStickersManager, AppDocsManager, ErrorService, AppInlineBotsManager, shouldFocusOnInteraction) { return { link: link, scope: { @@ -1497,6 +1497,11 @@ angular.module('myApp.directives', ['myApp.filters']) } }); + $scope.$on('stickers_changed', function () { + emojiTooltip.onStickersChanged(); + }); + + var composerEmojiPanel; if (emojiPanel) { composerEmojiPanel = new EmojiPanel(emojiPanel, { @@ -1506,9 +1511,6 @@ angular.module('myApp.directives', ['myApp.filters']) }); } - var peerPhotoCompiled = $compile(''); - var cachedPeerPhotos = {}; - var composer = new MessageComposer(messageField, { onTyping: function () { $scope.$emit('ui_typing'); @@ -1516,21 +1518,17 @@ angular.module('myApp.directives', ['myApp.filters']) getSendOnEnter: function () { return sendOnEnter; }, - getPeerImage: function (element, peerID, noReplace) { - if (cachedPeerPhotos[peerID] && !noReplace) { - element.replaceWith(cachedPeerPhotos[peerID]); - return; - } + dropdownDirective: function (element, callback) { var scope = $scope.$new(true); - scope.peerID = peerID; - peerPhotoCompiled(scope, function (clonedElement) { - cachedPeerPhotos[peerID] = clonedElement; + var clonedElement = $compile('
')(scope, function (clonedElement, scope) { element.replaceWith(clonedElement); + callback(scope, clonedElement); }); }, mentions: $scope.mentions, commands: $scope.commands, onMessageSubmit: onMessageSubmit, + onInlineResultSend: onInlineResultSend, onFilePaste: onFilePaste, onCommandSend: function (command) { $scope.$apply(function () { @@ -1544,6 +1542,21 @@ angular.module('myApp.directives', ['myApp.filters']) $(richTextarea).on('keydown keyup', updateHeight); } + $scope.$on('inline_results', function (e, inlineResults) { + if (inlineResults) { + var w = ((richTextarea || messageField).offsetWidth || 382) - 2; + var h = 80; + 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 () { @@ -1659,6 +1672,12 @@ angular.module('myApp.directives', ['myApp.filters']) return cancelEvent(e); } + function onInlineResultSend (qID) { + $scope.$apply(function () { + $scope.draftMessage.inlineResultID = qID; + }); + } + function updateValue () { if (richTextarea) { composer.onChange(); @@ -2063,7 +2082,7 @@ angular.module('myApp.directives', ['myApp.filters']) }) - .directive('myLoadGif', function(AppDocsManager) { + .directive('myLoadGif', function(AppDocsManager, $timeout) { return { link: link, @@ -2082,6 +2101,15 @@ angular.module('myApp.directives', ['myApp.filters']) $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); @@ -2092,6 +2120,16 @@ angular.module('myApp.directives', ['myApp.filters']) 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; } @@ -2105,8 +2143,9 @@ angular.module('myApp.directives', ['myApp.filters']) downloadPromise = AppDocsManager.downloadDoc($scope.document.id); downloadPromise.then(function () { - $scope.isActive = true; - $scope.$emit('ui_height'); + $timeout(function () { + $scope.isActive = true; + }, 200); }) } @@ -2330,8 +2369,8 @@ angular.module('myApp.directives', ['myApp.filters']) var src = 'https://maps.googleapis.com/maps/api/staticmap?sensor=false¢er=' + $scope.point['lat'] + ',' + $scope.point['long'] + '&zoom=15&size='+width+'x'+height+'&scale=2&key=' + apiKey; - ExternalResourcesManager.downloadImage(src).then(function (url) { - element.attr('src', url); + ExternalResourcesManager.downloadByURL(src).then(function (url) { + element.attr('src', url.valueOf()); }); } @@ -2831,7 +2870,7 @@ angular.module('myApp.directives', ['myApp.filters']) } }; - if (element[0].tagName == 'A') { + if (element[0].tagName == 'A' && !hasOnlick(element[0])) { element.on('click', function () { if (peerID > 0) { AppUsersManager.openUser(peerID, override); @@ -3317,44 +3356,67 @@ angular.module('myApp.directives', ['myApp.filters']) }) .directive('myArcProgress', function () { - var html = ''; + 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) { - element - .html(html) - .addClass('progress-arc-wrap'); + 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); - var svgEl = element[0].firstChild; - var circle = $('.progress-arc-circle', element); - var bar = $('.progress-arc-bar', element); + // Doesn't work without unique id for every gradient + var curNum = ++num; - var width = attrs.width || 40; - var radius = width * 0.86; - var stroke = width * 0.14; - var center = width / 2; + element + .html(html.replace('%id%', curNum)) + .addClass('progress-arc-wrap') + .addClass(intermediate ? 'progress-arc-intermediate' : 'progress-arc-percent') + .css({width: width, height: width}); - $(svgEl).attr('width', width); - $(svgEl).attr('height', width); - circle.attr('cx', center); - circle.attr('cy', center); - circle.attr('r', radius); - circle.css({strokeWidth: stroke}); + $(element[0].firstChild) + .attr('width', width) + .attr('height', width); - bar.attr('cx', center); - bar.attr('cy', center); - bar.attr('r', radius); - bar.css({strokeWidth: stroke}); + 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; - $scope.$watch('progress', function (newProgress) { - var progress = newProgress / 100.0; - progress = Math.max(0.0, Math.min(progress, 1.0)); - bar.css({strokeDasharray: (progress * fullLen) + ', ' + ((1 - progress) * fullLen)}); - }); - + 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); + }); + } } } }) @@ -3380,3 +3442,87 @@ angular.module('myApp.directives', ['myApp.filters']) }; }) + + .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, spritesheet, pos, categoryIndex; + var count = Math.min(5, codes.length); + var i, x, y; + + for (i = 0; i < count; i++) { + emoticonCode = codes[i]; + if (emoticonCode.code) { + emoticonCode = emoticonCode.code; + } + if (emoticonData = Config.Emoji[emoticonCode]) { + spritesheet = EmojiHelper.spritesheetPositions[emoticonCode]; + categoryIndex = spritesheet[0]; + pos = spritesheet[1]; + x = iconSize * spritesheet[3]; + y = iconSize * spritesheet[2]; + html.push('
  • :' + 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.document) { + AppDocsManager.downloadDoc(result.document.id); + } + if (result.type == 'photo' && result.photo) { + var photoSize = AppPhotosManager.choosePhotoSize(result.photo, result.thumbW, result.thumbH), + dim = calcImageInBox(photoSize.w, photoSize.h, result.thumbW, result.thumbH); + result.thumb = { + width: dim.w, + height: dim.h, + location: photoSize.location, + size: photoSize.size + }; + } + }) + }); + } + } + }) diff --git a/app/js/filters.js b/app/js/filters.js index 30869b5d..b840ce32 100644 --- a/app/js/filters.js +++ b/app/js/filters.js @@ -187,6 +187,9 @@ angular.module('myApp.filters', ['myApp.i18n']) .filter('formatSizeProgress', function ($filter, _) { var formatSizeFilter = $filter('formatSize'); return function (progress) { + if (!progress.total) { + return ''; + } var done = formatSizeFilter(progress.done, true), doneParts = done.split(' '), total = formatSizeFilter(progress.total), diff --git a/app/js/lib/config.js b/app/js/lib/config.js index 74a41205..8b6b5d79 100644 --- a/app/js/lib/config.js +++ b/app/js/lib/config.js @@ -36,7 +36,9 @@ Config.Modes = { webcrypto: location.search.indexOf('webcrypto=0')== -1, packed: location.protocol == 'app:' || location.protocol == 'chrome-extension:', ios_standalone: window.navigator.standalone && navigator.userAgent.match(/iOS|iPhone|iPad/), - chrome_packed: window.chrome && chrome.app && chrome.app.window && true || false + chrome_packed: window.chrome && chrome.app && chrome.app.window && true || false, + animations: true, + memory_only: false }; Config.Navigator = { diff --git a/app/js/lib/mtproto_wrapper.js b/app/js/lib/mtproto_wrapper.js index 103947af..55369e0c 100644 --- a/app/js/lib/mtproto_wrapper.js +++ b/app/js/lib/mtproto_wrapper.js @@ -384,11 +384,13 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) } function getFileStorage () { - if (TmpfsFileStorage.isAvailable()) { - return TmpfsFileStorage; - } - if (IdbFileStorage.isAvailable()) { - return IdbFileStorage; + if (!Config.Modes.memory_only) { + if (TmpfsFileStorage.isAvailable()) { + return TmpfsFileStorage; + } + if (IdbFileStorage.isAvailable()) { + return IdbFileStorage; + } } return MemoryFileStorage; } diff --git a/app/js/lib/ng_utils.js b/app/js/lib/ng_utils.js index 48a2f9ee..de37b647 100644 --- a/app/js/lib/ng_utils.js +++ b/app/js/lib/ng_utils.js @@ -963,10 +963,10 @@ angular.module('izhukov.utils', []) }; }) -.service('ExternalResourcesManager', function ($q, $http) { +.service('ExternalResourcesManager', function ($q, $http, $sce) { var urlPromises = {}; - function downloadImage (url) { + function downloadByURL (url) { if (urlPromises[url] !== undefined) { return urlPromises[url]; } @@ -974,12 +974,18 @@ angular.module('izhukov.utils', []) return urlPromises[url] = $http.get(url, {responseType: 'blob', transformRequest: null}) .then(function (response) { window.URL = window.URL || window.webkitURL; - return window.URL.createObjectURL(response.data); + var url = window.URL.createObjectURL(response.data); + return $sce.trustAsResourceUrl(url); + }, function (error) { + if (!Config.modes.chrome_packed) { + return $q.when($sce.trustAsResourceUrl(url)); + } + return $q.reject(error); }); } return { - downloadImage: downloadImage + downloadByURL: downloadByURL } }) diff --git a/app/js/lib/utils.js b/app/js/lib/utils.js index 1cc8b783..a55a83ff 100644 --- a/app/js/lib/utils.js +++ b/app/js/lib/utils.js @@ -60,6 +60,18 @@ function cancelEvent (event) { return false; } +function hasOnlick (element) { + if (element.onclick || + element.getAttribute('ng-click')) { + return true; + } + var events = $._data(element, 'events'); + if (events && (events.click || events.mousedown)) { + return true; + } + return false; +} + function getScrollWidth() { var outer = $('
    ').css({ position: 'absolute', @@ -289,7 +301,21 @@ function scrollToNode (scrollable, node, scroller) { } } +if (Config.Modes.animations && + typeof window.requestAnimationFrame == 'function') { + window.onAnimationFrameCallback = function (cb) { + return (function () { + window.requestAnimationFrame(cb); + }); + }; +} else { + window.onAnimationFrameCallback = function (cb) { + return cb; + }; +} + function onContentLoaded (cb) { + cb = onAnimationFrameCallback(cb); setZeroTimeout(cb); } diff --git a/app/js/locales/de-de.json b/app/js/locales/de-de.json index 95e434be..0acd9c0d 100644 --- a/app/js/locales/de-de.json +++ b/app/js/locales/de-de.json @@ -253,6 +253,7 @@ "conversation_media_video": "Video", "conversation_media_document": "Datei", "conversation_media_sticker": "Sticker", + "conversation_media_gif": "GIF", "conversation_media_audio": "Audio", "conversation_media_location": "Standort", "conversation_media_contact": "Kontakt", @@ -472,7 +473,9 @@ "password_recover_submit": "Absenden", "login_controller_unknown_country": "Unbekannt", "message_forwarded_message": "Weitergeleitete Nachricht", + "message_via_bot": "via {bot}", "message_forwarded_message_mobile": "Weitergeleitet von {from}, {date}", + "message_forwarded_via_message_mobile": "Weitergeleitet von {from} via {bot}, {date}", "message_attach_audio_message": "Sprachnachricht", "message_attach_audio_play": "Abspielen", "message_attach_document_open": "Öffnen", diff --git a/app/js/locales/it-it.json b/app/js/locales/it-it.json index dc00c2d5..a111bb45 100644 --- a/app/js/locales/it-it.json +++ b/app/js/locales/it-it.json @@ -253,6 +253,7 @@ "conversation_media_video": "Video", "conversation_media_document": "File", "conversation_media_sticker": "Sticker", + "conversation_media_gif": "GIF", "conversation_media_audio": "Audio", "conversation_media_location": "Posizione", "conversation_media_contact": "Contatto", @@ -472,7 +473,9 @@ "password_recover_submit": "Invia", "login_controller_unknown_country": "Sconosciuto", "message_forwarded_message": "Messaggio inoltrato", + "message_via_bot": "via {bot}", "message_forwarded_message_mobile": "Inoltrato da {from},{date}", + "message_forwarded_via_message_mobile": "Inoltrato da {from} via {bot}, {date}", "message_attach_audio_message": "Nota vocale", "message_attach_audio_play": "Riproduci", "message_attach_document_open": "Apri", diff --git a/app/js/locales/pt-br.json b/app/js/locales/pt-br.json index 19080847..f9beec8a 100644 --- a/app/js/locales/pt-br.json +++ b/app/js/locales/pt-br.json @@ -253,6 +253,7 @@ "conversation_media_video": "Vídeo", "conversation_media_document": "Arquivo", "conversation_media_sticker": "Sticker", + "conversation_media_gif": "GIF", "conversation_media_audio": "Áudio", "conversation_media_location": "Localização", "conversation_media_contact": "Contato", @@ -472,7 +473,9 @@ "password_recover_submit": "Enviar", "login_controller_unknown_country": "Desconhecido", "message_forwarded_message": "Encaminhar mensagem", + "message_via_bot": "via {bot}", "message_forwarded_message_mobile": "Encaminhado de {from}, {date}", + "message_forwarded_via_message_mobile": "Encaminhado de {from} via {bot}, {date}", "message_attach_audio_message": "Mensagem de voz", "message_attach_audio_play": "Tocar", "message_attach_document_open": "Abrir", diff --git a/app/js/locales/ru-ru.json b/app/js/locales/ru-ru.json index fccd9c81..293b7b8f 100644 --- a/app/js/locales/ru-ru.json +++ b/app/js/locales/ru-ru.json @@ -251,8 +251,9 @@ "conversation_you": "Вы", "conversation_media_photo": "Фотография", "conversation_media_video": "Видео", - "conversation_media_document": "File", + "conversation_media_document": "Файл", "conversation_media_sticker": "Стикер", + "conversation_media_gif": "GIF", "conversation_media_audio": "Аудио", "conversation_media_location": "Местоположение", "conversation_media_contact": "Контакт", @@ -357,7 +358,7 @@ "head_edit_messages": "Редактировать сообщения", "head_media_photos": "Фотографии", "head_media_video": "Видео", - "head_media_documents": "Files", + "head_media_documents": "Файлы", "head_media_audio": "Голосовые сообщения", "head_about": "О приложении", "head_clear_all": "Очистить всё", @@ -393,7 +394,7 @@ "im_media": "Медиа", "im_media_photos": "Фотографии", "im_media_video": "Видео", - "im_media_documents": "Files", + "im_media_documents": "Файлы", "im_media_audio": "Голосовые сообщения", "im_pluralize_participants": "{'one': '{} участник', 'few': '{} участника', 'many': '{} участников', 'other': '{} участников'}", "im_show_recent_messages": "Показать последние сообщения", @@ -472,7 +473,9 @@ "password_recover_submit": "Отправить", "login_controller_unknown_country": "Неизвестно", "message_forwarded_message": "Пересланное сообщение", + "message_via_bot": "via {bot}", "message_forwarded_message_mobile": "Переслано от {from}, {date}", + "message_forwarded_via_message_mobile": "Forwarded from {from} via {bot}, {date}", "message_attach_audio_message": "Запись голоса", "message_attach_audio_play": "Воспроизвести", "message_attach_document_open": "Открыть", diff --git a/app/js/message_composer.js b/app/js/message_composer.js index 3cbea068..367bd918 100644 --- a/app/js/message_composer.js +++ b/app/js/message_composer.js @@ -364,7 +364,7 @@ EmojiTooltip.prototype.createTooltip = function () { EmojiTooltip.prototype.selectCategory = function (cat, force) { - if (this.cat === cat && !force) { + if (!this.tab && this.cat === cat && !force) { return false; } $('.active', this.categoriesEl).removeClass('active'); @@ -566,6 +566,12 @@ EmojiTooltip.prototype.onStickersScroll = function (scrollable, scrollTop) { 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]; @@ -668,38 +674,19 @@ EmojiPanel.prototype.update = function () { function MessageComposer (textarea, options) { + var self = this; + this.textareaEl = $(textarea); this.setUpInput(); this.autoCompleteWrapEl = $('
    ').appendTo(document.body); - this.autoCompleteEl = $('').appendTo(this.autoCompleteWrapEl); + var autoCompleteEl = $('
    ').appendTo(this.autoCompleteWrapEl); - this.scroller = new Scroller(this.autoCompleteEl, {maxHeight: 180}); - - var self = this; - this.autoCompleteEl.on('mousedown', function (e) { - e = e.originalEvent || e; - var target = $(e.target), mention, code, command; - if (target[0].tagName != 'A') { - target = $(target[0].parentNode); - } - if (code = target.attr('data-code')) { - if (self.onEmojiSelected) { - self.onEmojiSelected(code, true); - } - EmojiHelper.pushPopularEmoji(code); - } - if (mention = target.attr('data-mention')) { - self.onMentionSelected(mention); - } - if (command = target.attr('data-command')) { - if (self.onCommandSelected) { - self.onCommandSelected(command); - } - self.hideSuggestions(); - } - return cancelEvent(e); + options.dropdownDirective(autoCompleteEl, function (scope, newAutoCompleteEl) { + self.autoCompleteEl = newAutoCompleteEl; + self.autoCompleteScope = scope; + self.setUpAutoComplete(); }); this.isActive = false; @@ -708,16 +695,20 @@ function MessageComposer (textarea, options) { this.onMessageSubmit = options.onMessageSubmit; this.getSendOnEnter = options.getSendOnEnter; this.onFilePaste = options.onFilePaste; + this.onCommandSend = options.onCommandSend; + this.onInlineResultSend = options.onInlineResultSend; this.mentions = options.mentions; this.commands = options.commands; - this.getPeerImage = options.getPeerImage; - this.onCommandSend = options.onCommandSend; } MessageComposer.autoCompleteRegEx = /(\s|^)(:|@|\/)([A-Za-z0-9\-\+\*@_]*)$/; MessageComposer.prototype.setUpInput = function () { + this.inlinePlaceholderWrap = $('
    ').prependTo(this.textareaEl[0].parentNode); + this.inlinePlaceholderPrefixEl = $('').appendTo(this.inlinePlaceholderWrap); + this.inlinePlaceholderEl = $('').appendTo(this.inlinePlaceholderWrap); + if ('contentEditable' in document.body) { this.setUpRich(); } else { @@ -732,6 +723,56 @@ MessageComposer.prototype.setUpInput = function () { } } +MessageComposer.prototype.setInlinePlaceholder = function (prefix, placeholder) { + this.inlinePlaceholderPrefix = prefix + this.inlinePlaceholderPrefixEl.html(encodeEntities(prefix)); + this.inlinePlaceholderEl.html(encodeEntities(placeholder)); + this.onChange(); +} + +MessageComposer.prototype.updateInlinePlaceholder = function () { + var prefix = this.inlinePlaceholderPrefix; + if (prefix) { + var value = this.textareaEl.val(); + this.inlinePlaceholderWrap.toggleClass('active', value == prefix); + } +} + +MessageComposer.prototype.setUpAutoComplete = function () { + this.scroller = new Scroller(this.autoCompleteEl, {maxHeight: 180}); + + var self = this; + this.autoCompleteEl.on('mousedown', function (e) { + e = e.originalEvent || e; + var target = $(e.target), mention, code, command, inlineID; + if (target[0].tagName != 'A') { + target = $(target[0].parentNode); + } + if (code = target.attr('data-code')) { + if (self.onEmojiSelected) { + self.onEmojiSelected(code, true); + } + EmojiHelper.pushPopularEmoji(code); + } + if (mention = target.attr('data-mention')) { + self.onMentionSelected(mention); + } + if (command = target.attr('data-command')) { + if (self.onCommandSelected) { + self.onCommandSelected(command); + } + self.hideSuggestions(); + } + if (inlineID = target.attr('data-inlineid')) { + if (self.onInlineResultSend) { + self.onInlineResultSend(inlineID); + } + self.hideSuggestions(); + } + return cancelEvent(e); + }); +} + MessageComposer.prototype.setUpRich = function () { this.textareaEl.hide(); this.richTextareaEl = $('
    '); @@ -763,7 +804,7 @@ MessageComposer.prototype.onKeyEvent = function (e) { if (this.keyupStarted === undefined) { this.keyupStarted = now; } - if (now - this.keyupStarted > 10000) { + if (now - this.keyupStarted > 3000 || true) { this.onChange(); } else { @@ -771,6 +812,8 @@ MessageComposer.prototype.onKeyEvent = function (e) { 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); } @@ -797,48 +840,57 @@ MessageComposer.prototype.onKeyEvent = function (e) { if (this.autocompleteShown) { if (e.keyCode == 38 || e.keyCode == 40) { // UP / DOWN var next = e.keyCode == 40; - var currentSelected = $(this.autoCompleteEl).find('.composer_autocomplete_option_active'); - - if (currentSelected.length) { - var currentSelectedWrap = currentSelected[0].parentNode; - var nextWrap = currentSelectedWrap[next ? 'nextSibling' : 'previousSibling']; - currentSelected.removeClass('composer_autocomplete_option_active'); - if (nextWrap) { - $(nextWrap).find('a').addClass('composer_autocomplete_option_active'); - this.scroller.scrollToNode(nextWrap); + 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); return cancelEvent(e); } } - var childNodes = this.autoCompleteEl[0].childNodes; - var nextWrap = childNodes[next ? 0 : childNodes.length - 1]; - this.scroller.scrollToNode(nextWrap); - $(nextWrap).find('a').addClass('composer_autocomplete_option_active'); + nextSel = allLIs[next ? 0 : allLIs.length - 1]; + this.scroller.scrollToNode(nextSel); + $(nextSel).addClass('composer_autocomplete_option_active'); return cancelEvent(e); } if (e.keyCode == 13 || e.keyCode == 9) { // Enter or Tab - var currentSelected = $(this.autoCompleteEl).find('.composer_autocomplete_option_active'); - if (!currentSelected.length && e.keyCode == 9) { - currentSelected = $(this.autoCompleteEl[0].childNodes[0]).find('a'); + var currentSel = $(this.autoCompleteEl).find('li.composer_autocomplete_option_active'); + if (!currentSel.length && e.keyCode == 9) { + currentSel = $(this.autoCompleteEl).find('li:first'); } - var code, mention, command; - if (code = currentSelected.attr('data-code')) { + currentSel = currentSel.find('a:first'); + var code, mention, command, inlineID; + if (code = currentSel.attr('data-code')) { this.onEmojiSelected(code, true); EmojiHelper.pushPopularEmoji(code); return cancelEvent(e); } - if (mention = currentSelected.attr('data-mention')) { + if (mention = currentSel.attr('data-mention')) { this.onMentionSelected(mention); return cancelEvent(e); } - if (command = currentSelected.attr('data-command')) { + 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); + } + return cancelEvent(e); + } checkSubmit = true; } } @@ -918,6 +970,13 @@ MessageComposer.prototype.checkAutocomplete = function (forceFull) { var value = textarea.value; } + if (value && + this.curInlineResults && + this.curInlineResults.text == value) { + this.showInlineSuggestions(this.curInlineResults); + return; + }; + if (!forceFull) { value = value.substr(0, pos); } @@ -1268,6 +1327,7 @@ MessageComposer.prototype.onChange = function (e) { delete this.keyupStarted; this.textareaEl.val(getRichValue(this.richTextareaEl[0])).trigger('change'); } + this.updateInlinePlaceholder(); } MessageComposer.prototype.getEmojiHtml = function (code, emoji) { @@ -1347,8 +1407,7 @@ MessageComposer.prototype.blur = function () { } } -MessageComposer.prototype.renderSuggestions = function (html) { - this.autoCompleteEl.html(html.join('')); +MessageComposer.prototype.renderSuggestions = function () { this.autoCompleteWrapEl.show(); this.scroller.reinit(); this.updatePosition(); @@ -1356,75 +1415,66 @@ MessageComposer.prototype.renderSuggestions = function (html) { } MessageComposer.prototype.showEmojiSuggestions = function (codes) { - var html = []; - var iconSize = Config.Mobile ? 26 : 20; - - var emoticonCode, emoticonData, spritesheet, pos, categoryIndex; - var count = Math.min(5, codes.length); - var i, x, y; - - for (i = 0; i < count; i++) { - emoticonCode = codes[i]; - if (emoticonCode.code) { - emoticonCode = emoticonCode.code; - } - if (emoticonData = Config.Emoji[emoticonCode]) { - spritesheet = EmojiHelper.spritesheetPositions[emoticonCode]; - categoryIndex = spritesheet[0]; - pos = spritesheet[1]; - x = iconSize * spritesheet[3]; - y = iconSize * spritesheet[2]; - html.push('
  • :' + encodeEntities(emoticonData[1][0]) + ':
  • '); - } - } - - this.renderSuggestions(html); + 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 html = []; - var user; - var count = users.length; - var i; - - for (i = 0; i < count; i++) { - user = users[i]; - html.push('
  • ' + user.rFullName + '@' + user.username + '
  • '); - } - - this.renderSuggestions(html); var self = this; - this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) { - self.getPeerImage($(element), element.getAttribute('data-user-id')); + setZeroTimeout(function () { + self.autoCompleteScope.$apply(function () { + self.autoCompleteScope.type = 'mentions'; + self.autoCompleteScope.mentionUsers = users; + }); + onContentLoaded(function () { + self.renderSuggestions(); + }); }); } MessageComposer.prototype.showCommandsSuggestions = function (commands) { - var html = []; - var command; - var count = Math.min(200, commands.length); - var i; + var self = this; + setZeroTimeout(function () { + self.autoCompleteScope.$apply(function () { + self.autoCompleteScope.type = 'commands'; + self.autoCompleteScope.commands = commands; + }); + onContentLoaded(function () { + self.renderSuggestions(); + }); + }); +} - for (i = 0; i < count; i++) { - command = commands[i]; - html.push('
  • ' + encodeEntities(command.value) + '' + command.rDescription + '
  • '); +MessageComposer.prototype.showInlineSuggestions = function (botResults) { + if (!botResults || !botResults.results.length) { + this.hideSuggestions(); + return; } - - this.renderSuggestions(html); - var self = this; - var usedImages = {}; - this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) { - var noReplace = true; - var botID = element.getAttribute('data-user-id'); - if (!usedImages[botID]) { - usedImages[botID] = true; - noReplace = false; - } - self.getPeerImage($(element), botID, noReplace); + 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(); @@ -1438,6 +1488,8 @@ MessageComposer.prototype.updatePosition = function () { } MessageComposer.prototype.hideSuggestions = function () { + // console.trace(); + // return; this.autoCompleteWrapEl.hide(); delete this.autocompleteShown; } diff --git a/app/js/messages_manager.js b/app/js/messages_manager.js index 6ddd8272..547bedcd 100644 --- a/app/js/messages_manager.js +++ b/app/js/messages_manager.js @@ -181,7 +181,10 @@ angular.module('myApp.services') } NotificationsManager.savePeerSettings(peerID, dialog.notify_settings); - ApiUpdatesManager.addChannelState(channelID, dialog.pts); + + if (dialog.pts) { + ApiUpdatesManager.addChannelState(channelID, dialog.pts); + } } function getTopMessages (limit) { @@ -1291,10 +1294,12 @@ angular.module('myApp.services') isChannel = AppPeersManager.isChannel(peerID), isMegagroup = isChannel && AppPeersManager.isMegagroup(peerID), asChannel = isChannel && !isMegagroup ? true : false, - entities = [], + entities = options.entities || [], message; - text = RichTextProcessor.parseMarkdown(text, entities); + if (!options.viaBotID) { + text = RichTextProcessor.parseMarkdown(text, entities); + } if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; @@ -1328,6 +1333,7 @@ angular.module('myApp.services') message: text, random_id: randomIDS, reply_to_msg_id: replyToMsgID, + via_bot_id: options.viaBotID, entities: entities, views: asChannel && 1, pending: true @@ -1359,21 +1365,34 @@ angular.module('myApp.services') if (replyToMsgID) { flags |= 1; } - if (entities.length) { - flags |= 8; - } if (asChannel) { flags |= 16; } + var apiPromise; + if (options.viaBotID) { + apiPromise = MtpApiManager.invokeApi('messages.sendInlineBotResult', { + flags: flags, + peer: AppPeersManager.getInputPeerByID(peerID), + random_id: randomID, + reply_to_msg_id: getMessageLocalID(replyToMsgID), + query_id: options.queryID, + id: options.resultID + }, sentRequestOptions); + } else { + if (entities.length) { + flags |= 8; + } + apiPromise = MtpApiManager.invokeApi('messages.sendMessage', { + flags: flags, + peer: AppPeersManager.getInputPeerByID(peerID), + message: text, + random_id: randomID, + reply_to_msg_id: getMessageLocalID(replyToMsgID), + entities: entities + }, sentRequestOptions) + } // console.log(flags, entities); - MtpApiManager.invokeApi('messages.sendMessage', { - flags: flags, - peer: AppPeersManager.getInputPeerByID(peerID), - message: text, - random_id: randomID, - reply_to_msg_id: getMessageLocalID(replyToMsgID), - entities: entities - }, sentRequestOptions).then(function (updates) { + apiPromise.then(function (updates) { if (updates._ == 'updateShortSentMessage') { message.flags = updates.flags; message.date = updates.date; @@ -1637,7 +1656,8 @@ angular.module('myApp.services') case 'inputMediaPhoto': media = { _: 'messageMediaPhoto', - photo: AppPhotosManager.getPhoto(inputMedia.id.id) + photo: AppPhotosManager.getPhoto(inputMedia.id.id), + caption: inputMedia.caption || '' }; break; @@ -1648,9 +1668,14 @@ angular.module('myApp.services') }; media = { _: 'messageMediaDocument', - 'document': doc + 'document': doc, + caption: inputMedia.caption || '' }; break; + + case 'messageMediaPending': + media = inputMedia; + break; } var flags = 0; @@ -1684,6 +1709,7 @@ angular.module('myApp.services') media: media, random_id: randomIDS, reply_to_msg_id: replyToMsgID, + via_bot_id: options.viaBotID, views: asChannel && 1, pending: true }; @@ -1718,13 +1744,26 @@ angular.module('myApp.services') sentRequestOptions.afterMessageID = pendingAfterMsgs[peerID].messageID; } - MtpApiManager.invokeApi('messages.sendMedia', { - flags: flags, - peer: AppPeersManager.getInputPeerByID(peerID), - media: inputMedia, - random_id: randomID, - reply_to_msg_id: getMessageLocalID(replyToMsgID) - }, sentRequestOptions).then(function (updates) { + var apiPromise; + if (options.viaBotID) { + apiPromise = MtpApiManager.invokeApi('messages.sendInlineBotResult', { + flags: flags, + peer: AppPeersManager.getInputPeerByID(peerID), + random_id: randomID, + reply_to_msg_id: getMessageLocalID(replyToMsgID), + query_id: options.queryID, + id: options.resultID + }, sentRequestOptions); + } else { + apiPromise = MtpApiManager.invokeApi('messages.sendMedia', { + flags: flags, + peer: AppPeersManager.getInputPeerByID(peerID), + media: inputMedia, + random_id: randomID, + reply_to_msg_id: getMessageLocalID(replyToMsgID) + }, sentRequestOptions); + } + apiPromise.then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates); }, function (error) { toggleError(true); @@ -3007,7 +3046,7 @@ angular.module('myApp.services') }; } }) - }) + }); return { getConversations: getConversations, diff --git a/app/js/services.js b/app/js/services.js index dd036724..18f5876c 100755 --- a/app/js/services.js +++ b/app/js/services.js @@ -11,7 +11,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) -.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, qSync, MtpApiFileManager, MtpApiManager, RichTextProcessor, ErrorService, Storage, _) { +.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, qSync, MtpApiManager, RichTextProcessor, ErrorService, Storage, _) { var users = {}, usernames = {}, userAccess = {}, @@ -575,7 +575,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) }) -.service('AppChatsManager', function ($q, $rootScope, $modal, _, MtpApiFileManager, MtpApiManager, AppUsersManager, AppPhotosManager, RichTextProcessor) { +.service('AppChatsManager', function ($q, $rootScope, $modal, _, MtpApiManager, AppUsersManager, AppPhotosManager, RichTextProcessor) { var chats = {}, usernames = {}, channelAccess = {}, @@ -833,7 +833,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) } }) -.service('AppPeersManager', function (qSync, AppUsersManager, AppChatsManager, MtpApiManager) { +.service('AppPeersManager', function ($q, qSync, AppUsersManager, AppChatsManager, MtpApiManager) { function getInputPeer (peerString) { var firstChar = peerString.charAt(0), @@ -944,6 +944,24 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) }); } + function resolveInlineMention (username) { + return resolveUsername(username).then(function (peerID) { + if (peerID > 0) { + var bot = AppUsersManager.getUser(peerID); + if (bot.pFlags.bot && bot.bot_inline_placeholder !== undefined) { + return qSync.when({ + id: peerID, + placeholder: bot.bot_inline_placeholder + }); + } + } + return $q.reject(); + }, function (error) { + error.handled = true; + return $q.reject(error); + }); + } + function getPeerID (peerString) { if (angular.isObject(peerString)) { return peerString.user_id @@ -990,6 +1008,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) getPeer: getPeer, getPeerPhoto: getPeerPhoto, resolveUsername: resolveUsername, + resolveInlineMention: resolveInlineMention, isChannel: isChannel, isMegagroup: isMegagroup, isBot: isBot @@ -1222,6 +1241,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) }); break; } + return $q.reject(error); }); } @@ -1907,7 +1927,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) } }) -.service('AppDocsManager', function ($sce, $rootScope, $modal, $window, $q, RichTextProcessor, MtpApiFileManager, FileManager, qSync) { +.service('AppDocsManager', function ($sce, $rootScope, $modal, $window, $q, $timeout, RichTextProcessor, MtpApiFileManager, FileManager, qSync) { var docs = {}, docsForHistory = {}, windowW = $(window).width(), @@ -2096,13 +2116,18 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) }); downloadPromise.then(function (blob) { - delete historyDoc.progress; if (blob) { FileManager.getFileCorrectUrl(blob, doc.mime_type).then(function (url) { - historyDoc.url = $sce.trustAsResourceUrl(url); + var trustedUrl = $sce.trustAsResourceUrl(url); + historyDoc.url = trustedUrl; + doc.url = trustedUrl; }) historyDoc.downloaded = true; } + historyDoc.progress.percent = 100; + $timeout(function () { + delete historyDoc.progress; + }); console.log('file save done'); }, function (e) { console.log('document download failed', e); @@ -2274,24 +2299,81 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) } }) -.service('AppStickersManager', function ($q, $rootScope, $modal, _, FileManager, MtpApiManager, MtpApiFileManager, AppDocsManager, Storage) { +.service('AppStickersManager', function ($q, $rootScope, $modal, _, FileManager, MtpApiManager, AppDocsManager, Storage, ApiUpdatesManager) { - var currentStickers = []; - var currentStickersets = []; - var installedStickersets = {}; - var stickersetItems = {}; - var applied = false; var started = false; + var applied = false; + var currentStickerSets = []; + + $rootScope.$on('apiUpdate', function (e, update) { + if (update._ != 'updateStickerSets' && + update._ != 'updateNewStickerSet' && + update._ != 'updateDelStickerSet' && + update._ != 'updateStickerSetsOrder') { + return false; + } + + return Storage.get('all_stickers').then(function (stickers) { + if (!stickers || + stickers.layer != Config.Schema.API.layer) { + $rootScope.$broadcast('stickers_changed'); + } + switch (update._) { + case 'updateNewStickerSet': + var fullSet = update.stickerset; + var set = fullSet.set; + var pos = false; + for (var i = 0, len = stickers.sets.length; i < len; i++) { + if (stickers.sets[i].id == set.id) { + pos = i; + break; + } + } + if (pos !== false) { + stickers.sets.splice(pos, 1); + } + set.pFlags.installed = true; + stickers.sets.unshift(set); + stickers.fullSets[set.id] = fullSet; + break; + + case 'updateDelStickerSet': + var set; + for (var i = 0, len = stickers.sets.length; i < len; i++) { + set = stickers.sets[i]; + if (set.id == update.id) { + set.pFlags.installed = false; + stickers.sets.splice(i, 1); + break; + } + } + delete stickers.fullSets[update.id]; + break; + + case 'updateStickerSetsOrder': + var order = update.order; + stickers.sets.sort(function (a, b) { + return order.indexOf(a.id) - order.indexOf(b.id); + }); + break; + } + stickers.hash = getStickerSetsHash(stickers.sets); + stickers.date = 0; + Storage.set({all_stickers: stickers}).then(function () { + $rootScope.$broadcast('stickers_changed'); + }); + }); + + }); return { start: start, + getStickers: getStickers, openStickersetLink: openStickersetLink, openStickerset: openStickerset, installStickerset: installStickerset, pushPopularSticker: pushPopularSticker, - getStickers: getStickers, - getStickerset: getStickerset, - getStickersImages: getStickersImages + getStickerset: getStickerset }; function start () { @@ -2301,104 +2383,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) } } - function getPopularStickers () { - return Storage.get('stickers_popular').then(function (popStickers) { - var result = []; - var i, len, docID; - if (popStickers && popStickers.length) { - for (i = 0, len = popStickers.length; i < len; i++) { - docID = popStickers[i][0]; - if (AppDocsManager.hasDoc(docID)) { - result.push({id: docID, rate: popStickers[i][1]}); - } - } - }; - return result; - }); - } - - function pushPopularSticker (id) { - getPopularStickers().then(function (popularStickers) { - var exists = false; - var count = popularStickers.length; - var result = []; - for (var i = 0; i < count; i++) { - if (popularStickers[i].id == id) { - exists = true; - popularStickers[i].rate++; - } - result.push([popularStickers[i].id, popularStickers[i].rate]); - } - if (exists) { - result.sort(function (a, b) { - return b[1] - a[1]; - }); - } else { - if (result.length > 15) { - result = result.slice(0, 15); - } - result.push([id, 1]); - } - ConfigStorage.set({stickers_popular: result}); - }); - } - - function processRawStickers(stickers) { - if (applied !== stickers.hash) { - applied = stickers.hash; - var i, j, len1, len2, doc, set, setItems, fullSet; - - currentStickersets = []; - currentStickers = []; - len1 = stickers.sets.length; - for (i = 0; i < len1; i++) { - set = stickers.sets[i]; - fullSet = stickers.fullSets[set.id]; - len2 = fullSet.documents.length; - setItems = []; - for (j = 0; j < len2; j++) { - doc = fullSet.documents[j]; - AppDocsManager.saveDoc(doc); - currentStickers.push(doc.id); - setItems.push(doc.id); - } - currentStickersets.push({ - id: set.id, - title: set.title, - short_name: set.short_name, - installed: (set.flags & (1 << 0)) > 0, - disabled: (set.flags & (1 << 1)) > 0, - official: (set.flags & (1 << 2)) > 0, - docIDs: setItems - }); - installedStickersets[set.id] = true; - } - } - - return getPopularStickers().then(function (popularStickers) { - var resultStickersets = currentStickersets; - if (popularStickers.length) { - resultStickersets = currentStickersets.slice(); - var setItems = []; - var i, len; - for (i = 0, len = popularStickers.length; i < len; i++) { - setItems.push(popularStickers[i].id); - } - resultStickersets.unshift({ - id: 0, - title: _('im_stickers_tab_recent_raw'), - short_name: '', - installed: true, - disabled: false, - official: false, - docIDs: setItems - }) - } - - return resultStickersets; - }); - } - function getStickers (force) { return Storage.get('all_stickers').then(function (stickers) { var layer = Config.Schema.API.layer; @@ -2424,7 +2408,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) return processRawStickers(newStickers); } - return getStickerSets(newStickers).then(function () { + return getStickerSets(newStickers, stickers && stickers.fullSets).then(function () { Storage.set({all_stickers: newStickers}); return processRawStickers(newStickers); }); @@ -2433,9 +2417,54 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) }) } - function getStickerSets (allStickers) { + function processRawStickers(stickers) { + if (applied !== stickers.hash) { + applied = stickers.hash; + var i, j, len1, len2, doc, set, docIDs, documents; + + currentStickerSets = []; + len1 = stickers.sets.length; + for (i = 0; i < len1; i++) { + set = stickers.sets[i]; + if (set.pFlags.disabled) { + continue; + } + documents = stickers.fullSets[set.id].documents; + len2 = documents.length; + docIDs = []; + for (j = 0; j < len2; j++) { + doc = documents[j]; + AppDocsManager.saveDoc(doc); + docIDs.push(doc.id); + } + set.docIDs = docIDs; + currentStickerSets.push(set); + } + } + + return getPopularStickers().then(function (popularStickers) { + var resultStickersets = currentStickerSets; + if (popularStickers.length) { + resultStickersets = currentStickerSets.slice(); + var docIDs = []; + var i, len; + for (i = 0, len = popularStickers.length; i < len; i++) { + docIDs.push(popularStickers[i].id); + } + resultStickersets.unshift({ + id: 0, + title: _('im_stickers_tab_recent_raw'), + short_name: '', + docIDs: docIDs + }); + } + return resultStickersets; + }); + } + + function getStickerSets (allStickers, prevCachedSets) { var promises = []; - var cachedSets = allStickers.fullSets || {}; + var cachedSets = prevCachedSets || allStickers.fullSets || {}; allStickers.fullSets = {}; angular.forEach(allStickers.sets, function (shortSet) { var fullSet = cachedSets[shortSet.id]; @@ -2457,24 +2486,46 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) return $q.all(promises); } - function downloadStickerThumb (docID) { - var doc = AppDocsManager.getDoc(docID); - var thumbLocation = angular.copy(doc.thumb.location); - thumbLocation.sticker = true; - return MtpApiFileManager.downloadSmallFile(thumbLocation).then(function (blob) { - return { - id: doc.id, - src: FileManager.getUrl(blob, 'image/webp') + function getPopularStickers () { + return Storage.get('stickers_popular').then(function (popStickers) { + var result = []; + var i, len, docID; + if (popStickers && popStickers.length) { + for (i = 0, len = popStickers.length; i < len; i++) { + docID = popStickers[i][0]; + if (AppDocsManager.hasDoc(docID)) { + result.push({id: docID, rate: popStickers[i][1]}); + } + } }; + return result; }); } - function getStickersImages () { - var promises = []; - angular.forEach(currentStickers, function (docID) { - promises.push(downloadStickerThumb (docID)); + function pushPopularSticker (id) { + getPopularStickers().then(function (popularStickers) { + var exists = false; + var count = popularStickers.length; + var result = []; + for (var i = 0; i < count; i++) { + if (popularStickers[i].id == id) { + exists = true; + popularStickers[i].rate++; + } + result.push([popularStickers[i].id, popularStickers[i].rate]); + } + if (exists) { + result.sort(function (a, b) { + return b[1] - a[1]; + }); + } else { + if (result.length > 15) { + result = result.slice(0, 15); + } + result.push([id, 1]); + } + ConfigStorage.set({stickers_popular: result}); }); - return $q.all(promises); } function getStickerset (inputStickerset) { @@ -2484,31 +2535,32 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) for (var i = 0; i < result.documents.length; i++) { AppDocsManager.saveDoc(result.documents[i]); } - result.installed = installedStickersets[result.set.id] !== undefined; return result; }); } - function installStickerset (set, uninstall) { + function installStickerset (fullSet, uninstall) { var method = uninstall ? 'messages.uninstallStickerSet' : 'messages.installStickerSet'; var inputStickerset = { _: 'inputStickerSetID', - id: set.id, - access_hash: set.access_hash + id: fullSet.set.id, + access_hash: fullSet.set.access_hash }; return MtpApiManager.invokeApi(method, { stickerset: inputStickerset, disabled: false }).then(function (result) { + var update; if (uninstall) { - delete installedStickersets[set.id]; + update = {_: 'updateDelStickerSet', id: fullSet.set.id}; } else { - installedStickersets[set.id] = true; + update = {_: 'updateNewStickerSet', stickerset: fullSet}; } - Storage.remove('all_stickers').then(function () { - getStickers(true); + ApiUpdatesManager.processUpdateMessage({ + _: 'updateShort', + update: update }); }); } @@ -2530,6 +2582,186 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) windowClass: 'stickerset_modal_window mobile_modal' }); } + + function getStickerSetsHash (stickerSets) { + var acc = 0, set; + for (var i = 0; i < stickerSets.length; i++) { + set = stickerSets[i]; + if (set.pFlags.disabled || !set.pFlags.installed) { + continue; + } + acc = ((acc * 20261) + 0x80000000 + set.hash) % 0x80000000; + } + return acc; + } +}) + +.service('AppInlineBotsManager', function (MtpApiManager, AppMessagesManager, AppDocsManager, AppPhotosManager, RichTextProcessor, AppUsersManager) { + + var inlineResults = {}; + + return { + sendInlineResult: sendInlineResult, + regroupWrappedResults: regroupWrappedResults, + getInlineResults: getInlineResults + }; + + function getInlineResults (botID, query, offset) { + return MtpApiManager.invokeApi('messages.getInlineBotResults', { + bot: AppUsersManager.getUserInput(botID), + query: query, + offset: offset + }, {timeout: 1, stopTime: -1, noErrorBox: true}).then(function(botResults) { + var queryID = botResults.query_id; + delete botResults._; + delete botResults.flags; + delete botResults.query_id; + + angular.forEach(botResults.results, function (result) { + var qID = queryID + '_' + result.id; + result.qID = qID; + result.botID = botID; + + result.rTitle = RichTextProcessor.wrapRichText(result.title, {noLinebreaks: true, noLinks: true}); + result.rDescription = RichTextProcessor.wrapRichText(result.description, {noLinebreaks: true, noLinks: true}); + result.initials = (result.url || result.title || result.type || '').substr(0, 1) + + if (result.document) { + AppDocsManager.saveDoc(result.document); + } + if (result.photo) { + AppPhotosManager.savePhoto(result.photo); + } + + inlineResults[qID] = result; + }); + return botResults; + }); + } + + function regroupWrappedResults (results, rowW, rowH) { + if (!results || + !results[0] || + results[0].type != 'photo' && results[0].type != 'gif') { + return; + } + var ratios = []; + angular.forEach(results, function (result) { + var w, h; + if (result._ == 'botInlineMediaResultDocument') { + w = result.document.w; + h = result.document.h; + } + else if (result._ == 'botInlineMediaResultPhoto') { + var photoSize = (result.photo.sizes || [])[0]; + w = photoSize && photoSize.w; + h = photoSize && photoSize.h; + } + else { + w = result.w; + h = result.h; + } + if (!w || !h) { + w = h = 1; + } + ratios.push(w / h); + }); + + var rows = []; + var curCnt = 0; + var curW = 0; + angular.forEach(ratios, function (ratio) { + var w = ratio * rowH; + curW += w; + if (!curCnt || curCnt < 4 && curW < (rowW * 1.1)) { + curCnt++; + } else { + rows.push(curCnt); + curCnt = 1; + curW = w; + } + }); + if (curCnt) { + rows.push(curCnt); + } + + var i = 0; + var thumbs = []; + var lastRowI = rows.length - 1; + angular.forEach(rows, function (rowCnt, rowI) { + var lastRow = rowI == lastRowI; + var curRatios = ratios.slice(i, i + rowCnt); + var sumRatios = 0; + angular.forEach(curRatios, function (ratio) { + sumRatios += ratio; + }); + angular.forEach(curRatios, function (ratio, j) { + var thumbH = rowH; + var thumbW = rowW * ratio / sumRatios; + var realW = thumbH * ratio; + if (lastRow && thumbW > realW) { + thumbW = realW; + } + var result = results[i + j]; + result.thumbW = Math.floor(thumbW); + result.thumbH = Math.floor(thumbH); + }); + + i += rowCnt; + }); + } + + function sendInlineResult (peerID, qID, options) { + var inlineResult = inlineResults[qID]; + if (inlineResult === undefined) { + return false; + } + var splitted = qID.split('_'); + var queryID = splitted.shift(); + var resultID = splitted.join('_'); + options = options || {}; + options.viaBotID = inlineResult.botID; + options.queryID = queryID; + options.resultID = resultID; + + if (inlineResult.send_message._ == 'botInlineMessageText') { + options.entities = inlineResult.send_message.entities; + AppMessagesManager.sendText(peerID, inlineResult.send_message.message, options); + } else { + var caption = ''; + if (inlineResult.send_message._ == 'botInlineMessageMediaAuto') { + caption = inlineResult.send_message.caption; + } + var inputMedia = false; + if (inlineResult._ == 'botInlineMediaResultDocument') { + var doc = inlineResult.document; + inputMedia = { + _: 'inputMediaDocument', + id: {_: 'inputDocument', id: doc.id, access_hash: doc.access_hash}, + caption: caption + }; + } + else if (inlineResult._ == 'botInlineMediaResultPhoto') { + var photo = inlineResult.photo; + inputMedia = { + _: 'inputMediaPhoto', + id: {_: 'inputPhoto', id: photo.id, access_hash: photo.access_hash}, + caption: caption + }; + } + if (!inputMedia) { + inputMedia = { + _: 'messageMediaPending', + type: inlineResult.type, + file_name: inlineResult.title || inlineResult.content_url || inlineResult.url, + size: 0, + progress: {percent: 30, total: 0} + }; + } + AppMessagesManager.sendOther(peerID, inputMedia, options); + } + } + }) .service('ApiUpdatesManager', function ($rootScope, MtpNetworkerFactory, AppUsersManager, AppChatsManager, AppPeersManager, MtpApiManager) { @@ -2828,6 +3060,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) } function addChannelState (channelID, pts) { + if (!pts) { + throw new Error('Add channel state without pts ' + channelID); + } if (channelStates[channelID] === undefined) { channelStates[channelID] = { pts: pts, @@ -2842,9 +3077,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) function getChannelState (channelID, pts) { if (channelStates[channelID] === undefined) { - if (!pts) { - throw new Error('Get channel empty state without pts ' + channelID); - } addChannelState(channelID, pts); } return channelStates[channelID]; @@ -3997,7 +4229,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) type: 'JUMP_EXT_URL', url: url }).then(function () { - window.open(url, '_blank'); + var target = '_blank'; + if (url.search('https://telegram.me/') === 0) { + target = '_self'; + } + window.open(url, target); }); return true; } @@ -4142,4 +4378,4 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) start: start, shareUrl: shareUrl }; -}) +}) \ No newline at end of file diff --git a/app/less/app.less b/app/less/app.less index 1fa9862c..f8e43dcd 100644 --- a/app/less/app.less +++ b/app/less/app.less @@ -420,20 +420,56 @@ a { display: block; border-radius: 100%; } -.progress-arc circle { - stroke-dashoffset: 0; - stroke: rgba(0,0,0,0.3); - stroke-width: 3px; -} .progress-arc .progress-arc-bar { - stroke: #FFF; + stroke-dashoffset: 0; transform-origin: center center; - transition: stroke-dasharray 500ms linear; + fill: transparent; + + .progress-arc-intermediate & { + stroke: #68a4d1; + + -webkit-animation: infinite_rotation 0.8s linear infinite; + -moz-animation: infinite_rotation 0.8s linear infinite; + -ms-animation: infinite_rotation 0.8s linear infinite; + animation: infinite_rotation 0.8s linear infinite; + } - -webkit-animation: infinite_rotation 2s linear infinite; - -moz-animation: infinite_rotation 2s linear infinite; - -ms-animation: infinite_rotation 2s linear infinite; - animation: infinite_rotation 2s linear infinite; + .composer_progress_icon & { + stroke: rgba(0,0,0,0.3); + } + + .progress-arc-percent & { + stroke: #FFF; + stroke: rgba(255,255,255,0.95); + + transition: stroke-dasharray 500ms linear; + + -webkit-animation: infinite_rotation 2s linear infinite; + -moz-animation: infinite_rotation 2s linear infinite; + -ms-animation: infinite_rotation 2s linear infinite; + animation: infinite_rotation 2s linear infinite; + } +} +.stop0 { + stop-opacity: 1.0; + stop-color: #68a4d1; + .composer_progress_icon & { + stop-color: rgba(0,0,0,0.3); + } +} +.stop60 { + stop-opacity: 1.0; + stop-color: #68a4d1; + .composer_progress_icon & { + stop-color: rgba(0,0,0,0.3); + } +} +.stop100 { + stop-opacity: 0.0; + stop-color: #68a4d1; + .composer_progress_icon & { + stop-color: rgba(0,0,0,0.3); + } } /* Infinite rotation */ @@ -926,13 +962,14 @@ a.tg_radio_on:hover i.icon-radio { background-position: -5px -10px; } .icon-tg-title { - width: 63px; - height: 16px; + width: 62px; + height: 14px; display: inline-block; vertical-align: middle; - - .image-2x('../img/Telegram.png', 63px, 16px); + background-image: url('../img/Telegram.svg'); + background-repeat: no-repeat; background-position: 0 0; + margin-top: 1px; } .login_head_submit_wrap, .login_head_submit_progress { @@ -1392,7 +1429,8 @@ a.im_dialog_selected { } .im_history { - padding: 20px 0 0 0; + // padding: 20px 0 0 0; + padding: 0; position: relative; } .im_message_unread_split { @@ -1403,12 +1441,14 @@ a.im_dialog_selected { margin: 10px 0; } .im_message_author, -.im_message_fwd_author { +.im_message_fwd_author, +.im_message_via_author { color: #3a6d99; font-weight: bold; } .non_osx .im_message_author, -.non_osx .im_message_fwd_author { +.non_osx .im_message_fwd_author, +.non_osx .im_message_via_author { font-size: 12px; } .im_message_author_via { @@ -1505,6 +1545,9 @@ div.im_message_video_thumb { .image-2x('../img/icons/IconsetW.png', 42px, 1171px); background-position: 0 -590px; + .is_2x & { + background-position: 0 -591px; + } } .im_message_geopoint { @@ -1999,6 +2042,10 @@ img.im_message_document_thumb { padding: 0 0 20px 10px; } +.im_message_date { + cursor: pointer; +} + div.im_message_author, div.im_message_body { display: block; @@ -2245,6 +2292,37 @@ a.im_message_fwd_photo { display: none; } } +.im_history_loading { + width: 60px; + margin: 0 auto; + visibility: hidden; + + &.vertical-aligned { + visibility: visible; + } +} + +.im_history_loading_more { + display: block; + width: 26px; + margin: 0 auto; + padding: 20px 0 0; + visibility: hidden; + + &.im_history_loading_more_active { + visibility: visible; + } +} +.im_history_loading_less { + display: block; + width: 26px; + margin: 0 auto; + visibility: hidden; + + &.im_history_loading_less_active { + visibility: visible; + } +} .im_send_panel_wrap { margin: 0 auto; @@ -2293,6 +2371,22 @@ textarea.im_message_field { height: 50px; resize: none; } +.im_inline_placeholder_wrap { + position: absolute; + margin-top: 2px; + white-space: nowrap; + pointer-events: none; + display: none; +} +.im_inline_placeholder_wrap.active { + display: block; +} +.im_inline_placeholder_prefix { + visibility: hidden; +} +.im_inline_placeholder { + color: #9aa2ab; +} .icon-online { background: #6ec26d; @@ -2423,7 +2517,27 @@ img.img_fullsize { } /* Message composer */ +.composer_progress_icon { + display: block; + opacity: 0; + position: absolute; + right: 3px; + top: 2px; + cursor: pointer; + padding: 0; + + width: 22px; + height: 22px; + margin-top: 1px; + transition: opacity cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.2s; + pointer-events: none; + + .composer_progress_enabled & { + opacity: 1; + } +} .composer_emoji_insert_btn { + opacity: 1; display: block; position: absolute; right: 3px; @@ -2434,7 +2548,13 @@ img.img_fullsize { width: 22px; height: 22px; margin-top: 1px; + transition: opacity cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.2s; + + .composer_progress_enabled & { + opacity: 0; + } } + .icon-emoji { display: inline-block; width: 22px; @@ -2757,6 +2877,7 @@ a.composer_emoji_btn { border-radius: 0; margin-top: -5px; margin-left: -1px; + width: 180px; } .composer_dropdown { @@ -2780,9 +2901,10 @@ a.composer_emoji_btn { } li a:hover, - li a.composer_autocomplete_option_active { + li.composer_autocomplete_option_active a { color: #52719a; background: #f2f6fa; + text-decoration: none; } } @@ -2802,7 +2924,7 @@ a.composer_emoji_btn { .composer_dropdown { li a:hover .composer_user_mention, - li a.composer_autocomplete_option_active .composer_user_mention { + li.composer_autocomplete_option_active a .composer_user_mention { color: #698192; } } @@ -2861,7 +2983,7 @@ a.composer_emoji_btn { text-overflow: ellipsis; } a.composer_command_option:hover .composer_command_desc, -a.composer_command_option.composer_autocomplete_option_active .composer_command_desc { +.composer_autocomplete_option_active a.composer_command_option .composer_command_desc { color: #698192; } .composer_command_desc .emoji { @@ -3031,6 +3153,64 @@ _:-ms-lang(x), .composer_rich_textarea:empty:focus:before { } } +.inline_results_wrap { + line-height: 0; +} +.inline_result_wrap { + display: block; +} +.inline_result_gif, +.inline_result_photo { + display: inline-block; +} +.inline_result_article { + display: block; +} +.inline_article_thumb_wrap { + width: 50px; + height: 50px; + margin-right: 10px; + pointer-events: none; +} +.inline_article_thumb { + max-width: 50px; + max-height: 50px; + line-height: 0; +} +.inline_article_thumb_initials { + background: rgba(0,0,0, 0.05); + line-height: 50px; + text-align: center; + font-size: 25px; + text-transform: uppercase; +} +.inline_article_content_wrap { + overflow: hidden; + pointer-events: none; +} +.inline_article_title { + font-weight: bold; +} + + +.composer_dropdown > li.inline_result_gif > a, +.composer_dropdown > li.inline_result_photo > a { + padding: 0; + line-height: 0; + display: block; + overflow: hidden; +} +.inline_result_gif .img_gif_video, +.inline_result_photo .inline_result_photo_image { + object-fit: cover; +} +.inline_result_gif_mtproto, +.inline_result_gif_http, +.inline_result_photo_mtproto, +.inline_result_photo_http { + pointer-events: none; +} + .error_modal_window { .modal-dialog { @@ -3553,64 +3733,83 @@ h5 { } /* Gif documents */ -.img_gif_with_progress_wrap { +.img_gif_image_wrap { position: relative; overflow: hidden; - float: left; - margin-top: 3px; - max-width: 100%; +} +.img_gif_meta { + background: rgba(0,0,0,0.3); + width: 40px; + height: 40px; + line-height: 0; + position: absolute; + z-index: 2; + border-radius: 50%; + overflow: hidden; + margin: 0 auto; + top: 50%; + left: 50%; + margin-left: -20px; + margin-top: -20px; + pointer-events: none; +} +.img_gif_label { + font-weight: bold; + color: #FFF; + display: block; + line-height: 40px; + font-size: 13px; + text-align: center; +} +.icon-cancel { + position: absolute; + top: 50%; + left: 50%; + margin-left: -9px; + margin-top: -1px; + + .icon-bar { + display: block; + width: 18px; + height: 2px; + background: #FFF; + transform-origin: 50% 50%; + + &:first-child { + .transform(rotate(-45deg)); + } + &:last-child { + .transform(translate3d(0,-2px,0) rotate(45deg)); + } + } } .img_gif_thumb { - -webkit-filter: blur(3px); - -moz-filter: blur(3px); - -o-filter: blur(3px); - -ms-filter: blur(3px); - filter: blur(3px); + -webkit-filter: blur(2px); + -moz-filter: blur(2px); + -o-filter: blur(2px); + -ms-filter: blur(2px); + filter: blur(2px); filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius='3'); - margin: -1px; - padding: 1px; + transform-origin: center center; + -webkit-transform-origin: center center; + -webkit-transform: scale(1.02, 1.02); + transform: scale(1.02, 1.02); + max-width: 100%; height: auto; } -.img_gif_image { - max-width: 100%; -} -.img_gif_info_wrap { - color: #fff; - font-size: 10px; - position: absolute; - bottom: 0; - left: 0; - right: 0; - padding: 4px; + +.img_gif_meta_contents { + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.2s; } -.img_gif_label, -.img_gif_size { - padding: 1px 8px; - background: rgba(0,0,0,0.5); - border-radius: 3px; - overflow: hidden; +.img_gif_meta_contents.ng-leave.ng-leave-active, +.img_gif_meta_contents.ng-enter { + opacity: 0; } - -.img_gif_progress_wrap { - position: absolute; - bottom: 0; - left: 0; - right: 0; - - .tg_progress { - background: rgba(0,0,0, 0.6); - border-color: rgba(0,0,0, 0.6); - border-width: 8px; - height: 18px; - border-radius: 0; - } - - .progress-bar { - background: #fff; - height: 2px; - } +.img_gif_meta_contents.ng-leave, +.img_gif_meta_contents.ng-enter.ng-enter-active { + opacity: 1; } .countries_modal_window { diff --git a/app/less/desktop.less b/app/less/desktop.less index b0027535..08b6f42e 100644 --- a/app/less/desktop.less +++ b/app/less/desktop.less @@ -748,7 +748,6 @@ a.footer_link.active:active { line-height: 18px; width: 100%; height: 39px; - padding: 13px 0 8px; overflow: hidden; -webkit-user-select: none; } @@ -758,7 +757,7 @@ a.footer_link.active:active { color: #999; max-width: 556px; margin: 0 auto; - padding: 0 81px 0 85px; + padding: 13px 81px 8px 85px; a.im_history_typing_author { color: #999; @@ -767,6 +766,10 @@ a.footer_link.active:active { } } +.im_history_loading_less { + margin-top: 5px; +} + /* Contacts modal */ .contacts_modal { &_window { diff --git a/app/partials/desktop/composer_dropdown.html b/app/partials/desktop/composer_dropdown.html new file mode 100644 index 00000000..f1d5e4ed --- /dev/null +++ b/app/partials/desktop/composer_dropdown.html @@ -0,0 +1,27 @@ +
    + + + + + + + +
    + +
    \ No newline at end of file diff --git a/app/partials/desktop/full_gif.html b/app/partials/desktop/full_gif.html index 4c96f20e..ae2f3024 100644 --- a/app/partials/desktop/full_gif.html +++ b/app/partials/desktop/full_gif.html @@ -3,11 +3,14 @@
    -
    -
    -
    GIF
    -
    +
    + + + + +
    +
    GIF
    diff --git a/app/partials/desktop/im.html b/app/partials/desktop/im.html index b8f04620..e75dc83a 100644 --- a/app/partials/desktop/im.html +++ b/app/partials/desktop/im.html @@ -90,8 +90,8 @@
    -
    - +
    +
    @@ -103,14 +103,18 @@
    -
    - - - - + +
    +
    +
    +
    +
    +
    +
    +
    @@ -118,9 +122,12 @@
    -
    +
    -
    +
    +
    +
    +
    @@ -175,7 +182,9 @@ -
    + + +
    @@ -189,23 +198,24 @@
    +
    - +
    - +
    - +
    @@ -234,4 +244,4 @@
    - \ No newline at end of file + diff --git a/app/partials/desktop/inline_results.html b/app/partials/desktop/inline_results.html new file mode 100644 index 00000000..161fe0a7 --- /dev/null +++ b/app/partials/desktop/inline_results.html @@ -0,0 +1,53 @@ + \ No newline at end of file diff --git a/app/partials/desktop/message.html b/app/partials/desktop/message.html index 769bf864..fa92172e 100644 --- a/app/partials/desktop/message.html +++ b/app/partials/desktop/message.html @@ -39,12 +39,12 @@
    - +
    - +