Browse Source

Merge branch 'layer-28'

master
Igor Zhukov 10 years ago
parent
commit
ca16589e8f
  1. 2
      app/js/app.js
  2. 42
      app/js/controllers.js
  3. 91
      app/js/directives.js
  4. 10
      app/js/lib/config.js
  5. 55
      app/js/lib/schema.tl.txt
  6. 15
      app/js/locales/en-us.json
  7. 200
      app/js/services.js
  8. 45
      app/less/app.less
  9. 16
      app/less/mobile.less
  10. 1
      app/partials/desktop/audio_player.html
  11. 23
      app/partials/desktop/chat_invite_link_modal.html
  12. 9
      app/partials/desktop/chat_modal.html
  13. 6
      app/partials/desktop/confirm_modal.html
  14. 4
      app/partials/desktop/dialog.html
  15. 6
      app/partials/desktop/error_modal.html
  16. 9
      app/partials/desktop/message.html
  17. 9
      app/partials/desktop/message_attach_geo.html
  18. 3
      app/partials/desktop/message_attach_map.html
  19. 5
      app/partials/desktop/message_attach_photo.html
  20. 20
      app/partials/desktop/message_attach_venue.html
  21. 21
      app/partials/desktop/message_attach_video.html
  22. 5
      app/partials/desktop/message_service.html
  23. 3
      app/partials/mobile/dialog.html
  24. 9
      app/partials/mobile/message.html
  25. 9
      app/partials/mobile/message_attach_geo.html
  26. 3
      app/partials/mobile/message_attach_map.html
  27. 5
      app/partials/mobile/message_attach_photo.html
  28. 7
      app/partials/mobile/message_attach_video.html
  29. 6
      app/partials/mobile/message_service.html

2
app/js/app.js

@ -28,7 +28,7 @@ angular.module('myApp', [
config(['$locationProvider', '$routeProvider', '$compileProvider', 'StorageProvider', function($locationProvider, $routeProvider, $compileProvider, StorageProvider) { config(['$locationProvider', '$routeProvider', '$compileProvider', 'StorageProvider', function($locationProvider, $routeProvider, $compileProvider, StorageProvider) {
$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|blob|filesystem|chrome-extension|app):|data:image\//); $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|blob|filesystem|chrome-extension|app):|data:image\//);
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|file|mailto|blob|filesystem|chrome-extension|app):|data:/); $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|file|tg|mailto|blob|filesystem|chrome-extension|app):|data:/);
if (Config.Modes.test) { if (Config.Modes.test) {
StorageProvider.setPrefix('t_'); StorageProvider.setPrefix('t_');

42
app/js/controllers.js

@ -2707,6 +2707,18 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}); });
}; };
$scope.inviteViaLink = function () {
var scope = $rootScope.$new();
scope.chatID = $scope.chatID;
$modal.open({
templateUrl: templateUrl('chat_invite_link_modal'),
controller: 'ChatInviteLinkModalController',
scope: scope,
windowClass: 'md_simple_modal_window mobile_modal'
});
}
$scope.photo = {}; $scope.photo = {};
@ -3606,6 +3618,36 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}; };
}) })
.controller('ChatInviteLinkModalController', function (_, $scope, $timeout, $modalInstance, AppChatsManager, ErrorService) {
$scope.exportedInvite = {link: _('group_invite_link_loading_raw')};
function updateLink (force) {
if (force) {
$scope.exportedInvite.revoking = true;
}
AppChatsManager.getChatInviteLink($scope.chatID, force).then(function (link) {
$scope.exportedInvite = {link: link};
$timeout(function () {
$scope.$broadcast('ui_invite_select');
}, 100);
})['finally'](function () {
delete $scope.exportedInvite.revoking;
});
}
$scope.revokeLink = function () {
ErrorService.confirm({
type: 'REVOKE_GROUP_INVITE_LINK'
}).then(function () {
updateLink(true);
})
}
updateLink();
})
.controller('ImportContactModalController', function ($scope, $modalInstance, $rootScope, AppUsersManager, ErrorService, PhonebookContactsService) { .controller('ImportContactModalController', function ($scope, $modalInstance, $rootScope, AppUsersManager, ErrorService, PhonebookContactsService) {
if ($scope.importContact === undefined) { if ($scope.importContact === undefined) {
$scope.importContact = {}; $scope.importContact = {};

91
app/js/directives.js

@ -403,25 +403,32 @@ angular.module('myApp.directives', ['myApp.filters'])
}) })
.directive('myMessagePhoto', function() { .directive('myMessagePhoto', function(AppPhotosManager) {
return { return {
templateUrl: templateUrl('message_attach_photo') scope: {
'media': '=myMessagePhoto',
'messageId': '=messageId'
},
templateUrl: templateUrl('message_attach_photo'),
link: function ($scope, element, attrs) {
$scope.openPhoto = AppPhotosManager.openPhoto;
}
}; };
}) })
.directive('myMessageVideo', function(AppVideoManager) { .directive('myMessageVideo', function(AppVideoManager) {
return { return {
scope: { scope: {
'video': '=myMessageVideo', 'media': '=myMessageVideo',
'messageId': '=messageId' 'messageId': '=messageId'
}, },
templateUrl: templateUrl('message_attach_video'), templateUrl: templateUrl('message_attach_video'),
link: function ($scope, element, attrs) { link: function ($scope, element, attrs) {
AppVideoManager.updateVideoDownloaded($scope.video.id); AppVideoManager.updateVideoDownloaded($scope.media.video.id);
$scope.videoSave = function () { $scope.videoSave = function () {
AppVideoManager.saveVideoFile($scope.video.id); AppVideoManager.saveVideoFile($scope.media.video.id);
}; };
$scope.videoOpen = function () { $scope.videoOpen = function () {
AppVideoManager.openVideo($scope.video.id, $scope.messageId); AppVideoManager.openVideo($scope.media.video.id, $scope.messageId);
}; };
} }
}; };
@ -447,9 +454,20 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
}; };
}) })
.directive('myMessageMap', function() { .directive('myMessageGeo', function() {
return {
scope: {
'media': '=myMessageGeo'
},
templateUrl: templateUrl('message_attach_geo')
};
})
.directive('myMessageVenue', function() {
return { return {
templateUrl: templateUrl('message_attach_map') scope: {
'venue': '=myMessageVenue'
},
templateUrl: templateUrl('message_attach_venue')
}; };
}) })
.directive('myMessageContact', function() { .directive('myMessageContact', function() {
@ -1936,27 +1954,27 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
}) })
.directive('myMapPoint', function(ExternalResourcesManager) { .directive('myGeoPointMap', function(ExternalResourcesManager) {
return { return {
link: link, link: link,
scope: { scope: {
point: '=' point: '=myGeoPointMap'
} }
}; };
function link ($scope, element, attrs) { function link ($scope, element, attrs) {
var width = element.attr('width') || 200;
var height = element.attr('height') || 200;
var apiKey = 'AIzaSyC32ij28dCa0YzEV_HqbWfIwTZQql-RNS0'; element.attr('src', 'img/blank.gif');
var src = 'https://maps.googleapis.com/maps/api/staticmap?sensor=false&center=' + $scope.point['lat'] + ',' + $scope.point['long'] + '&zoom=13&size=200x100&scale=2&key=' + apiKey; var apiKey = Config.ExtCredentials.gmaps.api_key;
var src = 'https://maps.googleapis.com/maps/api/staticmap?sensor=false&center=' + $scope.point['lat'] + ',' + $scope.point['long'] + '&zoom=15&size='+width+'x'+height+'&scale=2&key=' + apiKey;
ExternalResourcesManager.downloadImage(src).then(function (url) { ExternalResourcesManager.downloadImage(src).then(function (url) {
element.append('<img src="' + url + '" width="200" height="100"/>'); element.attr('src', url);
}); });
element.attr('href','https://maps.google.com/?q=' + $scope.point['lat'] + ',' + $scope.point['long']);
element.attr('target','_blank');
} }
}) })
@ -2600,7 +2618,7 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
}) })
.directive('myAudioPlayer', function ($timeout, $q, Storage, AppAudioManager, AppDocsManager, ErrorService) { .directive('myAudioPlayer', function ($timeout, $q, Storage, AppAudioManager, AppDocsManager, AppMessagesManager, ErrorService) {
var currentPlayer = false; var currentPlayer = false;
var audioVolume = 0.5; var audioVolume = 0.5;
@ -2628,7 +2646,8 @@ angular.module('myApp.directives', ['myApp.filters'])
return { return {
link: link, link: link,
scope: { scope: {
audio: '=' audio: '=',
message: '='
}, },
templateUrl: templateUrl('audio_player') templateUrl: templateUrl('audio_player')
}; };
@ -2710,6 +2729,12 @@ angular.module('myApp.directives', ['myApp.filters'])
checkPlayer($scope.mediaPlayer.player); checkPlayer($scope.mediaPlayer.player);
$scope.mediaPlayer.player.setVolume(audioVolume); $scope.mediaPlayer.player.setVolume(audioVolume);
$scope.mediaPlayer.player.play(); $scope.mediaPlayer.player.play();
if ($scope.message &&
!$scope.message.out &&
$scope.message.media_unread) {
AppMessagesManager.readMessages([$scope.message.id]);
}
}, 300); }, 300);
}); });
}) })
@ -2896,6 +2921,34 @@ angular.module('myApp.directives', ['myApp.filters'])
}; };
}) })
.directive('myCopyField', function () {
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('mySubmitOnEnter', function () { .directive('mySubmitOnEnter', function () {
return { return {

10
app/js/lib/config.js

File diff suppressed because one or more lines are too long

55
app/js/lib/schema.tl.txt

@ -23,13 +23,13 @@ inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_na
inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile; inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile;
inputMediaEmpty#9664f57f = InputMedia; inputMediaEmpty#9664f57f = InputMedia;
inputMediaUploadedPhoto#2dc53a7d file:InputFile = InputMedia; inputMediaUploadedPhoto#f7aff1c0 file:InputFile caption:string = InputMedia;
inputMediaPhoto#8f2ab2ec id:InputPhoto = InputMedia; inputMediaPhoto#e9bfb4f3 id:InputPhoto caption:string = InputMedia;
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia; inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia;
inputMediaUploadedVideo#133ad6f6 file:InputFile duration:int w:int h:int mime_type:string = InputMedia; inputMediaUploadedVideo#e13fd4bc file:InputFile duration:int w:int h:int caption:string = InputMedia;
inputMediaUploadedThumbVideo#9912dabf file:InputFile thumb:InputFile duration:int w:int h:int mime_type:string = InputMedia; inputMediaUploadedThumbVideo#96fb97dc file:InputFile thumb:InputFile duration:int w:int h:int caption:string = InputMedia;
inputMediaVideo#7f023ae6 id:InputVideo = InputMedia; inputMediaVideo#936a4ebd id:InputVideo caption:string = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#94254732 file:InputFile crop:InputPhotoCrop = InputChatPhoto; inputChatUploadedPhoto#94254732 file:InputFile crop:InputPhotoCrop = InputChatPhoto;
@ -87,7 +87,7 @@ chatEmpty#9ba2d800 id:int = Chat;
chat#6e9c9bc7 id:int title:string photo:ChatPhoto participants_count:int date:int left:Bool version:int = Chat; chat#6e9c9bc7 id:int title:string photo:ChatPhoto participants_count:int date:int left:Bool version:int = Chat;
chatForbidden#fb0ccc41 id:int title:string date:int = Chat; chatForbidden#fb0ccc41 id:int title:string date:int = Chat;
chatFull#630e61be id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings = ChatFull; chatFull#cade0791 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
@ -102,8 +102,8 @@ message#a7ab1991 flags:# id:int from_id:int to_id:Peer fwd_from_id:flags.2?int f
messageService#1d86f70e flags:int id:int from_id:int to_id:Peer date:int action:MessageAction = Message; messageService#1d86f70e flags:int id:int from_id:int to_id:Peer date:int action:MessageAction = Message;
messageMediaEmpty#3ded6320 = MessageMedia; messageMediaEmpty#3ded6320 = MessageMedia;
messageMediaPhoto#c8c45a2a photo:Photo = MessageMedia; messageMediaPhoto#3d8ce53d photo:Photo caption:string = MessageMedia;
messageMediaVideo#a2d24290 video:Video = MessageMedia; messageMediaVideo#5bcf1675 video:Video caption:string = MessageMedia;
messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia;
messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia; messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia;
messageMediaUnsupported#9f84f49e = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia;
@ -119,14 +119,14 @@ messageActionChatDeleteUser#b2ae9b0c user_id:int = MessageAction;
dialog#c1dd804a peer:Peer top_message:int read_inbox_max_id:int unread_count:int notify_settings:PeerNotifySettings = Dialog; dialog#c1dd804a peer:Peer top_message:int read_inbox_max_id:int unread_count:int notify_settings:PeerNotifySettings = Dialog;
photoEmpty#2331b22d id:long = Photo; photoEmpty#2331b22d id:long = Photo;
photo#22b56751 id:long access_hash:long user_id:int date:int caption:string geo:GeoPoint sizes:Vector<PhotoSize> = Photo; photo#c3838076 id:long access_hash:long user_id:int date:int geo:GeoPoint sizes:Vector<PhotoSize> = Photo;
photoSizeEmpty#e17e23c type:string = PhotoSize; photoSizeEmpty#e17e23c type:string = PhotoSize;
photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
videoEmpty#c10658a8 id:long = Video; videoEmpty#c10658a8 id:long = Video;
video#388fa391 id:long access_hash:long user_id:int date:int caption:string duration:int mime_type:string size:int thumb:PhotoSize dc_id:int w:int h:int = Video; video#ee9f4a4d id:long access_hash:long user_id:int date:int duration:int size:int thumb:PhotoSize dc_id:int w:int h:int = Video;
geoPointEmpty#1117dd5f = GeoPoint; geoPointEmpty#1117dd5f = GeoPoint;
geoPoint#2049d70c long:double lat:double = GeoPoint; geoPoint#2049d70c long:double lat:double = GeoPoint;
@ -209,7 +209,6 @@ inputMessagesFilterAudio#cfc87522 = MessagesFilter;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update; updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
updateMessageID#4e90bfd6 id:int random_id:long = Update; updateMessageID#4e90bfd6 id:int random_id:long = Update;
updateReadMessages#2e5ab668 messages:Vector<int> pts:int pts_count:int = Update;
updateDeleteMessages#a20db0e5 messages:Vector<int> pts:int pts_count:int = Update; updateDeleteMessages#a20db0e5 messages:Vector<int> pts:int pts_count:int = Update;
updateUserTyping#5c486927 user_id:int action:SendMessageAction = Update; updateUserTyping#5c486927 user_id:int action:SendMessageAction = Update;
updateChatUserTyping#9a65ea1f chat_id:int user_id:int action:SendMessageAction = Update; updateChatUserTyping#9a65ea1f chat_id:int user_id:int action:SendMessageAction = Update;
@ -243,7 +242,7 @@ upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
dcOption#2ec2a43c id:int hostname:string ip_address:string port:int = DcOption; dcOption#2ec2a43c id:int hostname:string ip_address:string port:int = DcOption;
config#68bac247 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int broadcast_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int disabled_features:Vector<DisabledFeature> = Config; config#4e32b894 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int broadcast_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int disabled_features:Vector<DisabledFeature> = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
@ -356,11 +355,11 @@ auth.sentAppCode#e325edcf phone_registered:Bool phone_code_hash:string send_call
sendMessageTypingAction#16bf744e = SendMessageAction; sendMessageTypingAction#16bf744e = SendMessageAction;
sendMessageCancelAction#fd5ec8f5 = SendMessageAction; sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
sendMessageRecordVideoAction#a187d66f = SendMessageAction; sendMessageRecordVideoAction#a187d66f = SendMessageAction;
sendMessageUploadVideoAction#92042ff7 = SendMessageAction; sendMessageUploadVideoAction#e9763aec progress:int = SendMessageAction;
sendMessageRecordAudioAction#d52f73f7 = SendMessageAction; sendMessageRecordAudioAction#d52f73f7 = SendMessageAction;
sendMessageUploadAudioAction#e6ac8a6f = SendMessageAction; sendMessageUploadAudioAction#f351d7ab progress:int = SendMessageAction;
sendMessageUploadPhotoAction#990a3c1a = SendMessageAction; sendMessageUploadPhotoAction#d1d34a26 progress:int = SendMessageAction;
sendMessageUploadDocumentAction#8faee98e = SendMessageAction; sendMessageUploadDocumentAction#aa0cd9e4 progress:int = SendMessageAction;
sendMessageGeoLocationAction#176f8ba1 = SendMessageAction; sendMessageGeoLocationAction#176f8ba1 = SendMessageAction;
sendMessageChooseContactAction#628cbc6f = SendMessageAction; sendMessageChooseContactAction#628cbc6f = SendMessageAction;
@ -450,6 +449,22 @@ account.passwordInputSettings#bcfc532c flags:# new_salt:flags.0?bytes new_passwo
auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
inputMediaVenue#2827a81a geo_point:InputGeoPoint title:string address:string provider:string venue_id:string = InputMedia;
messageMediaVenue#7912b71f geo:GeoPoint title:string address:string provider:string venue_id:string = MessageMedia;
receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage;
chatInviteEmpty#69df3769 = ExportedChatInvite;
chatInviteExported#fc2e05bc link:string = ExportedChatInvite;
chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
chatInvite#ce917dcd title:string = ChatInvite;
messageActionChatJoinedByLink#f89cf5e8 inviter_id:int = MessageAction;
updateReadMessagesContents#68c13933 messages:Vector<int> pts:int pts_count:int = Update;
---functions--- ---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -499,7 +514,7 @@ messages.search#7e9f2ab peer:InputPeer q:string filter:MessagesFilter min_date:i
messages.readHistory#b04f2510 peer:InputPeer max_id:int offset:int = messages.AffectedHistory; messages.readHistory#b04f2510 peer:InputPeer max_id:int offset:int = messages.AffectedHistory;
messages.deleteHistory#f4f8fb61 peer:InputPeer offset:int = messages.AffectedHistory; messages.deleteHistory#f4f8fb61 peer:InputPeer offset:int = messages.AffectedHistory;
messages.deleteMessages#a5f18925 id:Vector<int> = messages.AffectedMessages; messages.deleteMessages#a5f18925 id:Vector<int> = messages.AffectedMessages;
messages.receivedMessages#28abcb68 max_id:int = Vector<int>; messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool; messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool;
messages.sendMessage#9add8f26 flags:# peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long = messages.SentMessage; messages.sendMessage#9add8f26 flags:# peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long = messages.SentMessage;
messages.sendMedia#2d7923b1 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long = Updates; messages.sendMedia#2d7923b1 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long = Updates;
@ -601,3 +616,9 @@ account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings
auth.checkPassword#a63011e password_hash:bytes = auth.Authorization; auth.checkPassword#a63011e password_hash:bytes = auth.Authorization;
auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery;
auth.recoverPassword#4ea56e92 code:string = auth.Authorization; auth.recoverPassword#4ea56e92 code:string = auth.Authorization;
invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite;
messages.checkChatInvite#3eadb1bb hash:string = ChatInvite;
messages.importChatInvite#6c50051c hash:string = Updates;

15
app/js/locales/en-us.json

@ -23,6 +23,7 @@
"group_modal_menu_delete_chat": "Delete chat", "group_modal_menu_delete_chat": "Delete chat",
"group_modal_settings": "Settings", "group_modal_settings": "Settings",
"group_modal_notifications": "Notifications", "group_modal_notifications": "Notifications",
"group_modal_menu_share_link": "Invite to group via link",
"group_modal_members": "Members", "group_modal_members": "Members",
"group_modal_members_kick": "Remove", "group_modal_members_kick": "Remove",
@ -160,6 +161,12 @@
"group_edit_submit": "Save", "group_edit_submit": "Save",
"group_edit_submit_active": "Saving...", "group_edit_submit_active": "Saving...",
"group_invite_link_modal_title": "Invite link",
"group_invite_link_link_label": "Copy link",
"group_invite_link_loading": "Loading...",
"group_invite_revoke_active": "Revoking...",
"group_invite_revoke": "Revoke",
"confirm_modal_logout": "Are you sure you want to log out?", "confirm_modal_logout": "Are you sure you want to log out?",
"confirm_modal_update_reload": "A new version of Telegram Web has been downloaded. Launch it?", "confirm_modal_update_reload": "A new version of Telegram Web has been downloaded. Launch it?",
"confirm_modal_history_flush": "Are you sure? This can not be undone!", "confirm_modal_history_flush": "Are you sure? This can not be undone!",
@ -182,6 +189,8 @@
"confirm_modal_recovery_email_empty_md": "Warning! Are you sure you don't want to add a password recovery e-mail?\n\nIf you forget your password, you will lose access to your Telegram account", "confirm_modal_recovery_email_empty_md": "Warning! Are you sure you don't want to add a password recovery e-mail?\n\nIf you forget your password, you will lose access to your Telegram account",
"confirm_modal_abort_password_setup": "Abort two-step verification setup?", "confirm_modal_abort_password_setup": "Abort two-step verification setup?",
"confirm_modal_reset_account_md": "Are you sure?\nThis action can not be undone.\n\nYou will lose all your chats and messages, along with any media and files you shared, if you proceed with resetting your account.", "confirm_modal_reset_account_md": "Are you sure?\nThis action can not be undone.\n\nYou will lose all your chats and messages, along with any media and files you shared, if you proceed with resetting your account.",
"confirm_modal_join_group_link": "Do you want to join the group «{title}»?",
"confirm_modal_revoke_group_link": "Are you sure you want to revoke this link? Once you do, no one will be able to join the group using it.",
"confirm_modal_are_u_sure": "Are you sure?", "confirm_modal_are_u_sure": "Are you sure?",
"confirm_modal_logout_submit": "Log out", "confirm_modal_logout_submit": "Log out",
@ -244,6 +253,7 @@
"conversation_kicked_user": "removed {user}", "conversation_kicked_user": "removed {user}",
"conversation_invited_user_message": "invited user", "conversation_invited_user_message": "invited user",
"conversation_kicked_user_message": "removed user", "conversation_kicked_user_message": "removed user",
"conversation_joined_by_link": "joined group",
"conversation_message_sent": "sent you a message", "conversation_message_sent": "sent you a message",
"conversation_unknown_user": "Somebody", "conversation_unknown_user": "Somebody",
@ -257,6 +267,7 @@
"message_service_returned_to_group": "returned to group", "message_service_returned_to_group": "returned to group",
"message_service_kicked_user": "removed {user}", "message_service_kicked_user": "removed {user}",
"message_service_left_group": "left group", "message_service_left_group": "left group",
"message_service_joined_by_link": "joined group via invite link",
"message_service_unsupported_action": "Unsupported action {action}", "message_service_unsupported_action": "Unsupported action {action}",
"error_modal_warning_title": "Warning", "error_modal_warning_title": "Warning",
@ -279,7 +290,7 @@
"error_modal_firstname_invali_description": "The first name you entered is invalid.", "error_modal_firstname_invali_description": "The first name you entered is invalid.",
"error_modal_lastname_invalid_description": "The last name you entered is invalid.", "error_modal_lastname_invalid_description": "The last name you entered is invalid.",
"error_modal_phone_invalid_description": "The phone number you entered is invalid.", "error_modal_phone_invalid_description": "The phone number you entered is invalid.",
"error_modal_users_too_much_description": "You have selected too much users.", "error_modal_users_too_much_description": "Too many group members.",
"error_modal_photo_dimensions_invalid_description": "The photo dimensions are invalid, please select another file.", "error_modal_photo_dimensions_invalid_description": "The photo dimensions are invalid, please select another file.",
"error_modal_video_file_invalid_description": "The video file extension is invalid or unsupported, please select another file.", "error_modal_video_file_invalid_description": "The video file extension is invalid or unsupported, please select another file.",
"error_modal_photo_too_small_description": "The photo you provided is too small.", "error_modal_photo_too_small_description": "The photo you provided is too small.",
@ -302,6 +313,8 @@
"error_modal_recovery_na_description": "Since you haven't provided a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account.", "error_modal_recovery_na_description": "Since you haven't provided a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account.",
"error_modal_password_success_descripion": "Your password for Two-Step Verification is now active.", "error_modal_password_success_descripion": "Your password for Two-Step Verification is now active.",
"error_modal_password_disabled_descripion": "You have disabled Two-Step Verification.", "error_modal_password_disabled_descripion": "You have disabled Two-Step Verification.",
"error_modal_user_not_mutual_contact": "The user can be invited by his contact only",
"error_modal_invite_link_invalid": "The invite link is invalid",
"head_telegram": "Telegram", "head_telegram": "Telegram",

200
app/js/services.js

@ -599,6 +599,24 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}); });
} }
function getChatInviteLink (id, force) {
return getChatFull(id).then(function (chatFull) {
if (!force &&
chatFull.exported_invite &&
chatFull.exported_invite._ == 'chatInviteExported') {
return chatFull.exported_invite.link;
}
return MtpApiManager.invokeApi('messages.exportChatInvite', {
chat_id: id
}).then(function (exportedInvite) {
if (chatsFull[id] !== undefined) {
chatsFull[id].exported_invite = exportedInvite;
}
return exportedInvite.link;
});
});
}
function hasChat (id) { function hasChat (id) {
return angular.isObject(chats[id]); return angular.isObject(chats[id]);
} }
@ -628,10 +646,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
if (chatFull.participants && chatFull.participants._ == 'chatParticipants') { if (chatFull.participants && chatFull.participants._ == 'chatParticipants') {
MtpApiManager.getUserID().then(function (myID) { MtpApiManager.getUserID().then(function (myID) {
chatFull.isAdmin = (myID == chatFull.participants.admin_id);
angular.forEach(chatFull.participants.participants, function(participant){ angular.forEach(chatFull.participants.participants, function(participant){
participant.user = AppUsersManager.getUser(participant.user_id); participant.user = AppUsersManager.getUser(participant.user_id);
participant.canLeave = myID == participant.user_id; participant.canLeave = myID == participant.user_id;
participant.canKick = !participant.canLeave && (myID == chatFull.participants.admin_id || myID == participant.inviter_id); participant.canKick = !participant.canLeave && (chatFull.isAdmin || myID == participant.inviter_id);
}); });
}); });
} }
@ -717,6 +736,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
getChatFull: getChatFull, getChatFull: getChatFull,
getChatPhoto: getChatPhoto, getChatPhoto: getChatPhoto,
getChatString: getChatString, getChatString: getChatString,
getChatInviteLink: getChatInviteLink,
hasChat: hasChat, hasChat: hasChat,
wrapForFull: wrapForFull, wrapForFull: wrapForFull,
openChat: openChat openChat: openChat
@ -792,7 +812,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
}) })
.service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, PeersSelectService, Storage, FileManager, TelegramMeWebService, StatusManager, _) { .service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, $sce, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, PeersSelectService, Storage, FileManager, TelegramMeWebService, ErrorService, StatusManager, _) {
var messagesStorage = {}; var messagesStorage = {};
var messagesForHistory = {}; var messagesForHistory = {};
@ -1358,6 +1378,22 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
return historyStorage.readPromise; return historyStorage.readPromise;
} }
function readMessages (messageIDs) {
MtpApiManager.invokeApi('messages.readMessageContents', {
id: messageIDs
}).then(function (affectedMessages) {
ApiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateReadMessagesContents',
messages: messageIDs,
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
}
});
});
}
function flushHistory (inputPeer) { function flushHistory (inputPeer) {
// console.log('start flush'); // console.log('start flush');
var peerID = AppPeersManager.getPeerID(inputPeer), var peerID = AppPeersManager.getPeerID(inputPeer),
@ -1382,6 +1418,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
angular.forEach(apiMessages, function (apiMessage) { angular.forEach(apiMessages, function (apiMessage) {
apiMessage.unread = apiMessage.flags & 1 ? true : false; apiMessage.unread = apiMessage.flags & 1 ? true : false;
apiMessage.out = apiMessage.flags & 2 ? true : false; apiMessage.out = apiMessage.flags & 2 ? true : false;
apiMessage.media_unread = apiMessage.flags & 32 ? true : false;
messagesStorage[apiMessage.id] = apiMessage; messagesStorage[apiMessage.id] = apiMessage;
apiMessage.date -= serverTimeOffset; apiMessage.date -= serverTimeOffset;
@ -1885,6 +1922,46 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
return false; return false;
} }
function openChatInviteLink (hash) {
return MtpApiManager.invokeApi('messages.checkChatInvite', {
hash: hash
}).then(function (chatInvite) {
var chatTitle;
if (chatInvite._ == 'chatInviteAlready') {
AppChatsManager.saveApiChat(chatInvite.chat);
if (!chatInvite.chat.left) {
return $rootScope.$broadcast('history_focus', {
peerString: AppChatsManager.getChatString(chatInvite.chat.id)
});
}
chatTitle = chatInvite.chat.title;
} else {
chatTitle = chatInvite.title;
}
ErrorService.confirm({
type: 'JOIN_GROUP_BY_LINK',
title: chatTitle
}).then(function () {
return MtpApiManager.invokeApi('messages.importChatInvite', {
hash: hash
}).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates);
if (updates.updates && updates.updates.length) {
for (var i = 0, len = updates.updates.length, update; i < len; i++) {
update = updates.updates[i];
if (update._ == 'updateNewMessage') {
$rootScope.$broadcast('history_focus', {peerString: AppChatsManager.getChatString(update.message.to_id.chat_id)
});
break;
}
}
}
});
});
});
}
function getMessagePeer (message) { function getMessagePeer (message) {
var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0; var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0;
@ -1949,9 +2026,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
if (message.media) { if (message.media) {
if (message.media.caption &&
message.media.caption.length) {
message.media.rCaption = RichTextProcessor.wrapRichText(message.media.caption);
}
switch (message.media._) { switch (message.media._) {
case 'messageMediaPhoto': case 'messageMediaPhoto':
message.media.photo = AppPhotosManager.wrapForHistory(message.media.photo.id) message.media.photo = AppPhotosManager.wrapForHistory(message.media.photo.id);
break; break;
case 'messageMediaVideo': case 'messageMediaVideo':
@ -1966,6 +2048,22 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
message.media.audio = AppAudioManager.wrapForHistory(message.media.audio.id); message.media.audio = AppAudioManager.wrapForHistory(message.media.audio.id);
break; break;
case 'messageMediaGeo':
var mapUrl = 'https://maps.google.com/?q=' + message.media.geo['lat'] + ',' + message.media.geo['long'];
message.media.mapUrl = $sce.trustAsResourceUrl(mapUrl);
break;
case 'messageMediaVenue':
var mapUrl;
if (message.media.provider == 'foursquare' &&
message.media.venue_id) {
mapUrl = 'https://foursquare.com/v/' + encodeURIComponent(message.media.venue_id);
} else {
mapUrl = 'https://maps.google.com/?q=' + message.media.geo['lat'] + ',' + message.media.geo['long'];
}
message.media.mapUrl = $sce.trustAsResourceUrl(mapUrl);
break;
case 'messageMediaContact': case 'messageMediaContact':
message.media.rFullName = RichTextProcessor.wrapRichText( message.media.rFullName = RichTextProcessor.wrapRichText(
message.media.first_name + ' ' + (message.media.last_name || ''), message.media.first_name + ' ' + (message.media.last_name || ''),
@ -1975,7 +2073,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
case 'messageMediaWebPage': case 'messageMediaWebPage':
if (!message.media.webpage || if (!message.media.webpage ||
message.media.webpage._ == 'webPageEmpty') { message.media.webpage._ == 'webPageEmpty' ||
Config.Mobile) {
delete message.media; delete message.media;
break; break;
} }
@ -2187,7 +2286,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
break; break;
case 'messageMediaAudio': notificationMessage = _('conversation_media_audio_raw'); break; case 'messageMediaAudio': notificationMessage = _('conversation_media_audio_raw'); break;
case 'messageMediaGeo': notificationMessage = _('conversation_media_location_raw'); break; case 'messageMediaGeo':
case 'messageMediaVenue': notificationMessage = _('conversation_media_location_raw'); break;
case 'messageMediaContact': notificationMessage = _('conversation_media_contact_raw'); break; case 'messageMediaContact': notificationMessage = _('conversation_media_contact_raw'); break;
default: notificationMessage = _('conversation_media_attachment_raw'); break; default: notificationMessage = _('conversation_media_attachment_raw'); break;
} }
@ -2464,6 +2564,21 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
break; break;
case 'updateReadMessagesContents':
var messages = update.messages;
var len = messages.length;
var i, messageID, message, historyMessage;
for (i = 0; i < len; i++) {
messageID = messages[i];
if (message = messagesStorage[messageID]) {
delete message.media_unread;
}
if (historyMessage = messagesForHistory[messageID]) {
delete historyMessage.media_unread;
}
}
break;
case 'updateDeleteMessages': case 'updateDeleteMessages':
var dialogsUpdated = {}, var dialogsUpdated = {},
historiesUpdated = {}, historiesUpdated = {},
@ -2558,6 +2673,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
getSearch: getSearch, getSearch: getSearch,
getMessage: getMessage, getMessage: getMessage,
readHistory: readHistory, readHistory: readHistory,
readMessages: readMessages,
flushHistory: flushHistory, flushHistory: flushHistory,
deleteMessages: deleteMessages, deleteMessages: deleteMessages,
saveMessages: saveMessages, saveMessages: saveMessages,
@ -2565,6 +2681,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
sendFile: sendFile, sendFile: sendFile,
sendOther: sendOther, sendOther: sendOther,
forwardMessages: forwardMessages, forwardMessages: forwardMessages,
openChatInviteLink: openChatInviteLink,
getMessagePeer: getMessagePeer, getMessagePeer: getMessagePeer,
wrapForDialog: wrapForDialog, wrapForDialog: wrapForDialog,
wrapForHistory: wrapForHistory, wrapForHistory: wrapForHistory,
@ -3978,7 +4095,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
// resource path // resource path
"(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>\"'])?)?"; "(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>\"'])?)?";
var regExp = new RegExp('(^|\\s)((?:https?://)?telegram\\.me/|@)([a-zA-Z\\d_]{5,32})|(' + urlRegex + ')|(\\n)|(' + emojiRegex + ')|(^|\\s)(#[' + regexAlphaNumericChars + ']{2,64})', 'i'); var regExp = new RegExp('(^|\\s)(@)([a-zA-Z\\d_]{5,32})|(' + urlRegex + ')|(\\n)|(' + emojiRegex + ')|(^|\\s)(#[' + regexAlphaNumericChars + ']{2,64})', 'i');
var emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; var emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var youtubeRegex = /^(?:https?:\/\/)?(?:www\.)?youtu(?:|\.be|be\.com|\.b)(?:\/v\/|\/watch\\?v=|e\/|(?:\/\??#)?\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/; var youtubeRegex = /^(?:https?:\/\/)?(?:www\.)?youtu(?:|\.be|be\.com|\.b)(?:\/v\/|\/watch\\?v=|e\/|(?:\/\??#)?\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/;
@ -4049,14 +4166,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
if (match[3]) { // telegram.me links if (match[3]) { // telegram.me links
var contextUrl = !options.noLinks && siteMentions[contextSite]; var contextUrl = !options.noLinks && siteMentions[contextSite];
if (match[2] != '@' && contextExternal) {
contextUrl = false;
}
if (contextUrl) { if (contextUrl) {
var attr = ''; var attr = '';
if (options.highlightUsername && if (options.highlightUsername &&
options.highlightUsername.toLowerCase() == match[3].toLowerCase() && options.highlightUsername.toLowerCase() == match[3].toLowerCase()) {
match[2] == '@') {
attr = 'class="im_message_mymention"'; attr = 'class="im_message_mymention"';
} }
html.push( html.push(
@ -4108,6 +4221,19 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
url = (match[5] ? '' : protocol) + match[4]; url = (match[5] ? '' : protocol) + match[4];
} }
var tgMeMatch;
if (tld == 'me' &&
(tgMeMatch = url.match(/^https?:\/\/telegram\.me\/(.+)/))) {
var path = tgMeMatch[1].split('/');
switch (path[0]) {
case 'joinchat':
url = 'tg://join?invite=' + path[1];
break;
default:
url = 'tg://resolve?domain=' + path[0];
}
}
} else { // IP address } else { // IP address
url = (match[5] ? '' : 'http://') + match[4]; url = (match[5] ? '' : 'http://') + match[4];
} }
@ -5152,20 +5278,37 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}) })
.service('LocationParamsService', function ($rootScope, $routeParams, AppUsersManager) { .service('LocationParamsService', function ($rootScope, $routeParams, AppUsersManager, AppMessagesManager) {
function checkTgAddr () { var tgAddrRegEx = /^(web\+)?tg:(\/\/)?(.+)/;
if (!$routeParams.tgaddr) {
return; function checkLocationTgAddr () {
if ($routeParams.tgaddr) {
var matches = $routeParams.tgaddr.match(tgAddrRegEx);
if (matches) {
handleTgProtoAddr(matches[3]);
}
} }
var matches = $routeParams.tgaddr.match(/^(web\+)?tg:(\/\/)?resolve\?domain=(.+)$/); }
if (matches && matches[3]) {
AppUsersManager.resolveUsername(matches[3]).then(function (userID) { function handleTgProtoAddr (url) {
var matches;
if (matches = url.match(/^resolve\?domain=(.+)$/)) {
AppUsersManager.resolveUsername(matches[1]).then(function (userID) {
$rootScope.$broadcast('history_focus', { $rootScope.$broadcast('history_focus', {
peerString: AppUsersManager.getUserString(userID) peerString: AppUsersManager.getUserString(userID)
}); });
}); });
return true;
}
if (matches = url.match(/^join\?invite=(.+)$/)) {
AppMessagesManager.openChatInviteLink(matches[1]);
return true;
} }
return false;
} }
var started = !('registerProtocolHandler' in navigator); var started = !('registerProtocolHandler' in navigator);
@ -5181,8 +5324,25 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
navigator.registerProtocolHandler('web+tg', '#im?tgaddr=%s', 'Telegram Web'); navigator.registerProtocolHandler('web+tg', '#im?tgaddr=%s', 'Telegram Web');
} catch (e) {} } catch (e) {}
$rootScope.$on('$routeUpdate', checkTgAddr);
checkTgAddr(); $(document).on('click', function (event) {
var target = event.target;
if (target &&
target.tagName == 'A' &&
!target.onclick &&
!target.onmousedown) {
var href = $(target).attr('href') || target.href || '';
var match = href.match(tgAddrRegEx);
if (match) {
if (handleTgProtoAddr(match[3])) {
return cancelEvent(event);
}
}
}
});
$rootScope.$on('$routeUpdate', checkLocationTgAddr);
checkLocationTgAddr();
}; };
return { return {

45
app/less/app.less

@ -242,6 +242,9 @@ input[type="number"] {
padding: 0; padding: 0;
margin: 0 0 22px; margin: 0 0 22px;
} }
.md-textarea-group {
height: 66px;
}
.md-input-grouped { .md-input-grouped {
margin-bottom: 12px; margin-bottom: 12px;
} }
@ -283,6 +286,7 @@ input[type="number"] {
padding: 3px 0; padding: 3px 0;
margin: 3px 0 0; margin: 3px 0 0;
width: 100%; width: 100%;
resize: none;
.box-shadow(none); .box-shadow(none);
} }
@ -1457,8 +1461,23 @@ div.im_message_video_thumb {
overflow: hidden; overflow: hidden;
display: block; display: block;
position: relative; position: relative;
width: 200px; width: 300px;
height: 150px;
}
.im_message_venue_geopoint_wrap {
display: block;
position: relative;
overflow: hidden;
width: 100px;
height: 100px; height: 100px;
margin-right: 10px;
float: left;
border-radius: 2px;
background: #f2f2f2;
text-align: center;
line-height: 0;
} }
.icon-geo-point { .icon-geo-point {
@ -1467,7 +1486,7 @@ div.im_message_video_thumb {
top: 50%; top: 50%;
left: 50%; left: 50%;
margin-left: -8px; margin-left: -8px;
margin-top: -10px; margin-top: -15px;
width: 15px; width: 15px;
height: 19px; height: 19px;
@ -1673,8 +1692,7 @@ img.im_message_document_thumb {
width: 265px; width: 265px;
padding: 0 0 1px; padding: 0 0 1px;
} }
.im_message_document_actions, .im_message_document_actions {
.im_message_website_description {
width: 265px; width: 265px;
} }
.im_message_document_name { .im_message_document_name {
@ -1714,6 +1732,16 @@ img.im_message_document_thumb {
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.icon-audio-unread {
display: inline-block;
width: 6px;
height: 6px;
background: #4eabf1;
border: 0;
border-radius: 4px;
margin: 5px 5px 0 2px;
vertical-align: text-top;
}
.audio_player_meta { .audio_player_meta {
overflow: hidden; overflow: hidden;
vertical-align: text-top; vertical-align: text-top;
@ -1820,8 +1848,7 @@ img.im_message_document_thumb {
.im_message_document_name_wrap, .im_message_document_name_wrap,
.im_message_upload_progress_wrap, .im_message_upload_progress_wrap,
.im_message_download_progress_wrap, .im_message_download_progress_wrap,
.im_message_document_actions, .im_message_document_actions {
.im_message_website_description {
width: 207px; width: 207px;
} }
.im_message_document_name { .im_message_document_name {
@ -2001,6 +2028,12 @@ a.im_message_fwd_photo {
word-wrap: break-word; word-wrap: break-word;
line-height: 150%; line-height: 150%;
} }
.im_message_photo_caption,
.im_message_video_caption {
word-wrap: break-word;
line-height: 150%;
margin-top: 3px;
}
.im_message_mymention { .im_message_mymention {
background: #fff8cc; background: #fff8cc;
/*border-bottom: 1px solid #ffe222;*/ /*border-bottom: 1px solid #ffe222;*/

16
app/less/mobile.less

@ -576,6 +576,11 @@ html {
margin-top: 0; margin-top: 0;
} }
.im_message_geopoint {
width: 200px;
height: 100px;
}
a.im_message_photo_thumb, a.im_message_photo_thumb,
.im_message_video { .im_message_video {
margin-top: 0; margin-top: 0;
@ -660,6 +665,17 @@ html {
max-width: 250px; max-width: 250px;
} }
&_audio {
width: auto;
max-width: 200px;
& .audio_player_title_wrap {
width: auto;
font-size: 12px;
}
}
&_contact { &_contact {
width: 200px; width: 200px;

1
app/partials/desktop/audio_player.html

@ -11,6 +11,7 @@
<span ng-switch-when="true" ng-bind="::audio.file_name"></span> <span ng-switch-when="true" ng-bind="::audio.file_name"></span>
<span ng-switch-default my-i18n="message_attach_audio_message"></span> <span ng-switch-default my-i18n="message_attach_audio_message"></span>
</a> </a>
<i ng-if="::message.media_unread || false" ng-show="message.media_unread" class="icon icon-audio-unread"></i>
<div class="audio_player_meta" ng-if="!audio.downloaded || !(mediaPlayer.player.duration || audio.duration)" ng-switch="audio.progress.enabled"> <div class="audio_player_meta" ng-if="!audio.downloaded || !(mediaPlayer.player.duration || audio.duration)" ng-switch="audio.progress.enabled">
<span ng-switch-when="true" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span> <span ng-switch-when="true" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span>
<span ng-switch-default class="audio_player_size" ng-bind="audio.size | formatSize"></span> <span ng-switch-default class="audio_player_size" ng-bind="audio.size | formatSize"></span>

23
app/partials/desktop/chat_invite_link_modal.html

@ -0,0 +1,23 @@
<div class="md_simple_modal_wrap" my-modal-position>
<div class="md_simple_modal_body">
<div class="modal_simple_form">
<h4 my-i18n="group_invite_link_modal_title"></h4>
<div class="md-input-group md-textarea-group" my-labeled-input>
<label class="md-input-label" my-i18n="group_invite_link_link_label"></label>
<textarea class="md-input" ng-model="exportedInvite.link" rows="2" my-copy-field="'ui_invite_select'"></textarea>
</div>
</div>
</div>
<div class="md_simple_modal_footer">
<button class="btn btn-md" ng-click="$dismiss()" my-i18n="modal_cancel"></button>
<button class="btn btn-md btn-md-primary" ng-class="{disabled: exportedInvite.revoking}" ng-click="revokeLink()" ng-bind="exportedInvite.revoking ? 'group_invite_revoke_active' : 'group_invite_revoke' | i18n" ng-disabled="exportedInvite.revoking"></button>
</div>
</div>

9
app/partials/desktop/chat_modal.html

@ -51,6 +51,15 @@
</div> </div>
<div class="md_modal_iconed_section_wrap md_modal_iconed_section_link" ng-if="chatFull.chat._ != 'chatForbidden' && !chatFull.chat.left && chatFull.isAdmin">
<!-- <i class="md_modal_section_icon md_modal_section_icon_more"></i> -->
<div class="md_modal_section_link_wrap">
<a class="md_modal_section_link" ng-click="inviteViaLink()" my-i18n="group_modal_menu_share_link"></a>
</div>
</div>
<div class="md_modal_iconed_section_wrap md_modal_iconed_section_toggle"> <div class="md_modal_iconed_section_wrap md_modal_iconed_section_toggle">
<i class="md_modal_section_icon md_modal_section_icon_notification"></i> <i class="md_modal_section_icon md_modal_section_icon_notification"></i>

6
app/partials/desktop/confirm_modal.html

@ -47,6 +47,12 @@
<div ng-switch-when="RECOVERY_EMAIL_EMPTY" my-i18n="confirm_modal_recovery_email_empty_md"></div> <div ng-switch-when="RECOVERY_EMAIL_EMPTY" my-i18n="confirm_modal_recovery_email_empty_md"></div>
<div ng-switch-when="PASSWORD_ABORT_SETUP" my-i18n="confirm_modal_abort_password_setup"></div> <div ng-switch-when="PASSWORD_ABORT_SETUP" my-i18n="confirm_modal_abort_password_setup"></div>
<div ng-switch-when="RESET_ACCOUNT" my-i18n="confirm_modal_reset_account_md"></div> <div ng-switch-when="RESET_ACCOUNT" my-i18n="confirm_modal_reset_account_md"></div>
<div ng-switch-when="JOIN_GROUP_BY_LINK" my-i18n="confirm_modal_join_group_link">
<my-i18n-param name="title"><strong ng-bind="title"></strong></my-i18n-param>
</div>
<div ng-switch-when="REVOKE_GROUP_INVITE_LINK" my-i18n="confirm_modal_revoke_group_link"></div>
<span ng-switch-default ng-switch="message.length > 0"> <span ng-switch-default ng-switch="message.length > 0">
<span ng-switch-when="true" ng-bind="message"></span> <span ng-switch-when="true" ng-bind="message"></span>
<span ng-switch-default my-i18n="confirm_modal_are_u_sure"></span> <span ng-switch-default my-i18n="confirm_modal_are_u_sure"></span>

4
app/partials/desktop/dialog.html

@ -70,6 +70,7 @@
</span> </span>
<span ng-switch-when="messageMediaAudio" my-i18n="conversation_media_audio"></span> <span ng-switch-when="messageMediaAudio" my-i18n="conversation_media_audio"></span>
<span ng-switch-when="messageMediaGeo" my-i18n="conversation_media_location"></span> <span ng-switch-when="messageMediaGeo" my-i18n="conversation_media_location"></span>
<span ng-switch-when="messageMediaVenue" my-i18n="conversation_media_location"></span>
<span ng-switch-when="messageMediaContact" my-i18n="conversation_media_contact"></span> <span ng-switch-when="messageMediaContact" my-i18n="conversation_media_contact"></span>
</span> </span>
@ -92,6 +93,9 @@
<my-i18n-param name="user"><span my-user-link="dialogMessage.action.user_id"></span></my-i18n-param> <my-i18n-param name="user"><span my-user-link="dialogMessage.action.user_id"></span></my-i18n-param>
</span> </span>
</span> </span>
<span ng-switch-when="messageActionChatJoinedByLink" my-i18n="conversation_joined_by_link"></span>
</span> </span>
<span class="im_dialog_message_text" ng-if="dialogMessage.message.length" ng-bind-html="dialogMessage.richMessage"></span> <span class="im_dialog_message_text" ng-if="dialogMessage.message.length" ng-bind-html="dialogMessage.richMessage"></span>

6
app/partials/desktop/error_modal.html

@ -38,7 +38,11 @@
<span ng-switch-when="USERNAME_OCCUPIED" my-i18n="error_modal_username_occupied_description"></span> <span ng-switch-when="USERNAME_OCCUPIED" my-i18n="error_modal_username_occupied_description"></span>
<span ng-switch-when="MEDIA_TYPE_NOT_SUPPORTED" my-i18n="error_modal_media_not_supported_description"></span> <span ng-switch-when="MEDIA_TYPE_NOT_SUPPORTED" my-i18n="error_modal_media_not_supported_description"></span>
<span ng-switch-when="USERNAME_NOT_OCCUPIED" my-i18n="error_modal_username_not_found_description"></span> <span ng-switch-when="USERNAME_NOT_OCCUPIED" my-i18n="error_modal_username_not_found_description"></span>
<span ng-switch-when="PASSWORD_RECOVERY_NA" my-i18n="error_modal_recovery_na_description"></span> <span ng-switch-when="USER_NOT_MUTUAL_CONTACT" my-i18n="error_modal_user_not_mutual_contact"></span>
<span ng-switch-when="INVITE_HASH_INVALID" my-i18n="error_modal_invite_link_invalid"></span>
<span ng-switch-when="INVITE_HASH_EXPIRED" my-i18n="error_modal_invite_link_invalid"></span>
<span ng-switch-when="INVITE_HASH_EMPTY" my-i18n="error_modal_invite_link_invalid"></span>
<div ng-switch-default ng-switch="error.code"> <div ng-switch-default ng-switch="error.code">

9
app/partials/desktop/message.html

@ -54,11 +54,12 @@
<!-- <div class="im_message_external_embed_wrap" ng-if="::historyMessage.richUrlEmbed || false" my-external-embed="historyMessage.richUrlEmbed"></div> --> <!-- <div class="im_message_external_embed_wrap" ng-if="::historyMessage.richUrlEmbed || false" my-external-embed="historyMessage.richUrlEmbed"></div> -->
<div ng-if="::historyMessage.media || historyMessage.id < 0 ? true : false" class="im_message_media" ng-switch="historyMessage.media._"> <div ng-if="::historyMessage.media || historyMessage.id < 0 ? true : false" class="im_message_media" ng-switch="historyMessage.media._">
<div ng-switch-when="messageMediaPhoto" my-message-photo></div> <div ng-switch-when="messageMediaPhoto" my-message-photo="historyMessage.media" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaVideo" my-message-video="historyMessage.media.video" message-id="historyMessage.id"></div> <div ng-switch-when="messageMediaVideo" my-message-video="historyMessage.media" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaDocument" my-message-document="historyMessage.media.document" message-id="historyMessage.id"></div> <div ng-switch-when="messageMediaDocument" my-message-document="historyMessage.media.document" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaAudio" class="im_message_audio" my-audio-player audio="historyMessage.media.audio"></div> <div ng-switch-when="messageMediaAudio" class="im_message_audio" my-audio-player audio="historyMessage.media.audio" message="historyMessage"></div>
<div ng-switch-when="messageMediaGeo" my-message-map></div> <div ng-switch-when="messageMediaGeo" my-message-geo="historyMessage.media"></div>
<div ng-switch-when="messageMediaVenue" my-message-venue="historyMessage.media"></div>
<div ng-switch-when="messageMediaContact" class="im_message_contact" my-message-contact></div> <div ng-switch-when="messageMediaContact" class="im_message_contact" my-message-contact></div>
<div ng-switch-when="messageMediaWebPage" class="im_message_webpage" my-message-webpage="historyMessage.media.webpage" message-id="historyMessage.id"></div> <div ng-switch-when="messageMediaWebPage" class="im_message_webpage" my-message-webpage="historyMessage.media.webpage" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaPending" my-message-pending></div> <div ng-switch-when="messageMediaPending" my-message-pending></div>

9
app/partials/desktop/message_attach_geo.html

@ -0,0 +1,9 @@
<a ng-href="{{::media.mapUrl}}" target="_blank" class="im_message_geopoint">
<i class="icon icon-geo-point"></i>
<img
class="im_message_venue_geopoint_image"
my-geo-point-map="media.geo"
width="300"
height="150"
/>
</a>

3
app/partials/desktop/message_attach_map.html

@ -1,3 +0,0 @@
<a my-map-point point="historyMessage.media.geo" class="im_message_geopoint">
<i class="icon icon-geo-point"></i>
</a>

5
app/partials/desktop/message_attach_photo.html

@ -1,7 +1,8 @@
<a class="im_message_photo_thumb" ng-click="openPhoto(historyMessage.media.photo.id, {m: historyMessage.id})" ng-style="::{width: historyMessage.media.photo.thumb.width + 'px'}" ng-mouseover="preloadPhoto(historyMessage.media.photo.id)"> <a class="im_message_photo_thumb" ng-click="openPhoto(media.photo.id, {m: messageId})" ng-style="::{width: media.photo.thumb.width + 'px'}" ng-mouseover="preloadPhoto(media.photo.id)">
<img <img
class="im_message_photo_thumb" class="im_message_photo_thumb"
my-load-thumb my-load-thumb
thumb="historyMessage.media.photo.thumb" thumb="media.photo.thumb"
/> />
</a> </a>
<div ng-if="::media.rCaption" class="im_message_photo_caption" ng-bind-html="::media.rCaption"></div>

20
app/partials/desktop/message_attach_venue.html

@ -0,0 +1,20 @@
<div class="im_message_venue clearfix">
<a ng-href="{{::venue.mapUrl}}" target="_blank" class="im_message_venue_geopoint_wrap">
<i class="icon icon-geo-point"></i>
<img
class="im_message_venue_geopoint_image"
my-geo-point-map="venue.geo"
width="100"
height="100"
/>
</a>
<div class="im_message_venue_info">
<div class="im_message_venue_title_wrap">
<a ng-href="{{::venue.mapUrl}}" target="_blank" class="im_message_document_name" ng-bind="::venue.title"></a>
</div>
<div class="im_message_venue_address" ng-bind="::venue.address"></div>
</div>
</div>

21
app/partials/desktop/message_attach_video.html

@ -1,34 +1,35 @@
<div class="im_message_video im_message_document_thumbed"> <div class="im_message_video im_message_document_thumbed">
<a class="im_message_video_thumb" ng-click="videoOpen()" ng-style="::{width: video.thumb.width + 'px'}"> <a class="im_message_video_thumb" ng-click="videoOpen()" ng-style="::{width: media.video.thumb.width + 'px'}">
<span class="im_message_video_duration" ng-bind="::video.duration | duration"></span> <span class="im_message_video_duration" ng-bind="::media.video.duration | duration"></span>
<i class="icon icon-videoplay"></i> <i class="icon icon-videoplay"></i>
<img <img
class="im_message_video_thumb im_message_video_thumb_blurred" class="im_message_video_thumb im_message_video_thumb_blurred"
my-load-thumb my-load-thumb
thumb="video.thumb" thumb="media.video.thumb"
/> />
</a> </a>
<div class="im_message_document_info"> <div class="im_message_document_info">
<div class="im_message_document_name_wrap"> <div class="im_message_document_name_wrap">
<span class="im_message_document_name" my-i18n="message_attach_video_video"></span> <span class="im_message_document_name" my-i18n="message_attach_video_video"></span>
<span class="im_message_document_size" ng-if="!video.progress.enabled" ng-bind="::video.size | formatSize"></span> <span class="im_message_document_size" ng-if="!media.video.progress.enabled" ng-bind="::media.video.size | formatSize"></span>
<span class="im_message_document_size" ng-if="video.progress.enabled" ng-bind="video.progress | formatSizeProgress"></span> <span class="im_message_document_size" ng-if="media.video.progress.enabled" ng-bind="media.video.progress | formatSizeProgress"></span>
</div> </div>
<div class="im_message_document_actions" ng-if="!video.progress.enabled"> <div class="im_message_document_actions" ng-if="!media.video.progress.enabled">
<a href="" ng-click="videoSave()" ng-switch="video.downloaded"> <a href="" ng-click="videoSave()" ng-switch="media.video.downloaded">
<span ng-switch-when="true" my-i18n="message_attach_video_save"></span> <span ng-switch-when="true" my-i18n="message_attach_video_save"></span>
<span ng-switch-default my-i18n="message_attach_video_download"></span> <span ng-switch-default my-i18n="message_attach_video_download"></span>
</a> </a>
<a href="" ng-click="videoOpen()" my-i18n="message_attach_video_play"></a> <a href="" ng-click="videoOpen()" my-i18n="message_attach_video_play"></a>
</div> </div>
<div class="clearfix im_message_cancelable_progress_wrap" ng-if="video.progress.enabled"> <div class="clearfix im_message_cancelable_progress_wrap" ng-if="media.video.progress.enabled">
<a class="im_message_media_progress_cancel pull-right" ng-click="video.progress.cancel()" my-i18n="modal_cancel"></a> <a class="im_message_media_progress_cancel pull-right" ng-click="media.video.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap"> <div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress"> <div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: video.progress.percent + '%'}"></div> <div class="progress-bar progress-bar-success" ng-style="{width: media.video.progress.percent + '%'}"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div ng-if="::media.rCaption" class="im_message_video_caption" ng-bind-html="::media.rCaption"></div>

5
app/partials/desktop/message_service.html

@ -18,6 +18,9 @@
</span> </span>
<span ng-switch-default my-i18n="message_service_left_group"></span> <span ng-switch-default my-i18n="message_service_left_group"></span>
</span> </span>
<span ng-switch-when="messageActionChatJoinedByLink" my-i18n="message_service_joined_by_link"></span>
<span ng-switch-default ng-bind="'message_service_unsupported_action' | i18n:historyMessage.action._"></span> <span ng-switch-default my-i18n="message_service_unsupported_action">
<my-i18n-param name="action"><span ng-bind="historyMessage.action._"></span></my-i18n-param>
</span>
</span> </span>

3
app/partials/mobile/dialog.html

@ -77,6 +77,7 @@
</span> </span>
<span ng-switch-when="messageMediaAudio" my-i18n="conversation_media_audio"></span> <span ng-switch-when="messageMediaAudio" my-i18n="conversation_media_audio"></span>
<span ng-switch-when="messageMediaGeo" my-i18n="conversation_media_location"></span> <span ng-switch-when="messageMediaGeo" my-i18n="conversation_media_location"></span>
<span ng-switch-when="messageMediaVenue" my-i18n="conversation_media_location"></span>
<span ng-switch-when="messageMediaContact" my-i18n="conversation_media_contact"></span> <span ng-switch-when="messageMediaContact" my-i18n="conversation_media_contact"></span>
</span> </span>
@ -101,6 +102,8 @@
</span> </span>
</span> </span>
<span ng-switch-when="messageActionChatJoinedByLink" my-i18n="conversation_joined_by_link"></span>
<span class="im_dialog_message_text" ng-if="dialogMessage.message.length" ng-bind-html="dialogMessage.richMessage"></span> <span class="im_dialog_message_text" ng-if="dialogMessage.message.length" ng-bind-html="dialogMessage.richMessage"></span>
</div> </div>
</div> </div>

9
app/partials/mobile/message.html

@ -29,7 +29,7 @@
<span class="im_message_date" ng-bind="::historyMessage.date | time"></span> <span class="im_message_date" ng-bind="::historyMessage.date | time"></span>
</div> </div>
<div class="im_message_body" ng-class="::{im_message_body_media: !!historyMessage.media}"> <div class="im_message_body" ng-class="::{im_message_body_media: !!historyMessage.media && !historyMessage.media.rCaption}">
<a class="im_message_author" my-user-link="historyMessage.from_id" short="!historyMessage.to_id.chat_id" color="historyMessage.to_id.chat_id > 0" no-watch="true"></a> <a class="im_message_author" my-user-link="historyMessage.from_id" short="!historyMessage.to_id.chat_id" color="historyMessage.to_id.chat_id > 0" no-watch="true"></a>
@ -42,11 +42,12 @@
<div ng-if="::historyMessage.media || false" class="im_message_media" ng-switch="historyMessage.media._"> <div ng-if="::historyMessage.media || false" class="im_message_media" ng-switch="historyMessage.media._">
<div ng-switch-when="messageMediaPhoto" my-message-photo></div> <div ng-switch-when="messageMediaPhoto" my-message-photo="historyMessage.media" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaVideo" my-message-video="historyMessage.media.video" message-id="historyMessage.id"></div> <div ng-switch-when="messageMediaVideo" my-message-video="historyMessage.media" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaDocument" my-message-document="historyMessage.media.document" message-id="historyMessage.id"></div> <div ng-switch-when="messageMediaDocument" my-message-document="historyMessage.media.document" message-id="historyMessage.id"></div>
<div ng-switch-when="messageMediaAudio" class="im_message_audio" my-audio-player audio="historyMessage.media.audio"></div> <div ng-switch-when="messageMediaAudio" class="im_message_audio" my-audio-player audio="historyMessage.media.audio"></div>
<div ng-switch-when="messageMediaGeo" my-message-map></div> <div ng-switch-when="messageMediaGeo" my-message-geo="historyMessage.media"></div>
<div ng-switch-when="messageMediaVenue" my-message-geo="historyMessage.media"></div>
<div ng-switch-when="messageMediaContact" class="im_message_contact" my-message-contact></div> <div ng-switch-when="messageMediaContact" class="im_message_contact" my-message-contact></div>
<div ng-switch-when="messageMediaPending" my-message-pending></div> <div ng-switch-when="messageMediaPending" my-message-pending></div>
<div ng-switch-when="messageMediaUnsupported"> <div ng-switch-when="messageMediaUnsupported">

9
app/partials/mobile/message_attach_geo.html

@ -0,0 +1,9 @@
<a ng-href="{{::media.mapUrl}}" target="_blank" class="im_message_geopoint">
<i class="icon icon-geo-point"></i>
<img
class="im_message_venue_geopoint_image"
my-geo-point-map="media.geo"
width="200"
height="100"
/>
</a>

3
app/partials/mobile/message_attach_map.html

@ -1,3 +0,0 @@
<a my-map-point point="historyMessage.media.geo" class="im_message_geopoint">
<i class="icon icon-geo-point"></i>
</a>

5
app/partials/mobile/message_attach_photo.html

@ -1,7 +1,8 @@
<a class="im_message_photo_thumb" href="" ng-click="openPhoto(historyMessage.media.photo.id, {m: historyMessage.id})" ng-style="::{width: historyMessage.media.photo.thumb.width + 'px'}" ng-mouseover="preloadPhoto(historyMessage.media.photo.id)"> <a class="im_message_photo_thumb" ng-click="openPhoto(media.photo.id, {m: messageId})" ng-style="::{width: media.photo.thumb.width + 'px'}" ng-mouseover="preloadPhoto(media.photo.id)">
<img <img
class="im_message_photo_thumb" class="im_message_photo_thumb"
my-load-thumb my-load-thumb
thumb="historyMessage.media.photo.thumb" thumb="media.photo.thumb"
/> />
</a> </a>
<div ng-if="::media.rCaption" class="im_message_photo_caption" ng-bind-html="::media.rCaption"></div>

7
app/partials/mobile/message_attach_video.html

@ -1,11 +1,12 @@
<div class="im_message_video im_message_document_thumbed"> <div class="im_message_video im_message_document_thumbed">
<a class="im_message_video_thumb" href="" ng-click="videoOpen()" ng-style="::{width: video.thumb.width + 'px'}"> <a class="im_message_video_thumb" href="" ng-click="videoOpen()" ng-style="::{width: media.video.thumb.width + 'px'}">
<span class="im_message_video_duration" ng-bind="::video.duration | duration"></span> <span class="im_message_video_duration" ng-bind="::media.video.duration | duration"></span>
<i class="icon icon-videoplay"></i> <i class="icon icon-videoplay"></i>
<img <img
class="im_message_video_thumb im_message_video_thumb_blurred" class="im_message_video_thumb im_message_video_thumb_blurred"
my-load-thumb my-load-thumb
thumb="video.thumb" thumb="media.video.thumb"
/> />
</a> </a>
</div> </div>
<div ng-if="::media.rCaption" class="im_message_video_caption" ng-bind-html="::media.rCaption"></div>

6
app/partials/mobile/message_service.html

@ -19,5 +19,9 @@
<span ng-switch-default my-i18n="message_service_left_group"></span> <span ng-switch-default my-i18n="message_service_left_group"></span>
</span> </span>
<span ng-switch-default ng-bind="'message_service_unsupported_action' | i18n:historyMessage.action._"></span> <span ng-switch-when="messageActionChatJoinedByLink" my-i18n="message_service_joined_by_link"></span>
<span ng-switch-default my-i18n="message_service_unsupported_action">
<my-i18n-param name="action"><span ng-bind="historyMessage.action._"></span></my-i18n-param>
</span>
</span> </span>

Loading…
Cancel
Save