Browse Source

Layer 28 draft

master
Igor Zhukov 9 years ago
parent
commit
1dd82a2ce8
  1. 31
      app/js/controllers.js
  2. 80
      app/js/directives.js
  3. 10
      app/js/lib/config.js
  4. 57
      app/js/lib/schema.tl.txt
  5. 7
      app/js/locales/en-us.json
  6. 135
      app/js/services.js
  7. 45
      app/less/app.less
  8. 16
      app/less/mobile.less
  9. 1
      app/partials/desktop/audio_player.html
  10. 23
      app/partials/desktop/chat_invite_link_modal.html
  11. 9
      app/partials/desktop/chat_modal.html
  12. 1
      app/partials/desktop/dialog.html
  13. 9
      app/partials/desktop/message.html
  14. 9
      app/partials/desktop/message_attach_geo.html
  15. 3
      app/partials/desktop/message_attach_map.html
  16. 7
      app/partials/desktop/message_attach_photo.html
  17. 20
      app/partials/desktop/message_attach_venue.html
  18. 21
      app/partials/desktop/message_attach_video.html
  19. 1
      app/partials/mobile/dialog.html
  20. 9
      app/partials/mobile/message.html
  21. 9
      app/partials/mobile/message_attach_geo.html
  22. 3
      app/partials/mobile/message_attach_map.html
  23. 7
      app/partials/mobile/message_attach_photo.html
  24. 9
      app/partials/mobile/message_attach_video.html

31
app/js/controllers.js

@ -2707,6 +2707,18 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -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 = {};
@ -3606,6 +3618,25 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -3606,6 +3618,25 @@ angular.module('myApp.controllers', ['myApp.i18n'])
};
})
.controller('ChatInviteLinkModalController', function (_, $scope, $modalInstance, AppChatsManager) {
$scope.exportedInvite = {link: _('group_invite_link_loading_raw')};
$scope.updateLink = function (force) {
if (force) {
$scope.exportedInvite.revoking = true;
}
AppChatsManager.getChatInviteLink($scope.chatID, force).then(function (link) {
$scope.exportedInvite = {link: link};
})['finally'](function () {
delete $scope.exportedInvite.revoking;
});
}
$scope.updateLink();
})
.controller('ImportContactModalController', function ($scope, $modalInstance, $rootScope, AppUsersManager, ErrorService, PhonebookContactsService) {
if ($scope.importContact === undefined) {
$scope.importContact = {};

80
app/js/directives.js

@ -403,25 +403,32 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -403,25 +403,32 @@ angular.module('myApp.directives', ['myApp.filters'])
})
.directive('myMessagePhoto', function() {
.directive('myMessagePhoto', function(AppPhotosManager) {
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) {
return {
scope: {
'video': '=myMessageVideo',
'media': '=myMessageVideo',
'messageId': '=messageId'
},
templateUrl: templateUrl('message_attach_video'),
link: function ($scope, element, attrs) {
AppVideoManager.updateVideoDownloaded($scope.video.id);
AppVideoManager.updateVideoDownloaded($scope.media.video.id);
$scope.videoSave = function () {
AppVideoManager.saveVideoFile($scope.video.id);
AppVideoManager.saveVideoFile($scope.media.video.id);
};
$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']) @@ -447,9 +454,20 @@ angular.module('myApp.directives', ['myApp.filters'])
}
};
})
.directive('myMessageMap', function() {
.directive('myMessageGeo', function() {
return {
templateUrl: templateUrl('message_attach_map')
scope: {
'media': '=myMessageGeo'
},
templateUrl: templateUrl('message_attach_geo')
};
})
.directive('myMessageVenue', function() {
return {
scope: {
'venue': '=myMessageVenue'
},
templateUrl: templateUrl('message_attach_venue')
};
})
.directive('myMessageContact', function() {
@ -1936,27 +1954,27 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -1936,27 +1954,27 @@ angular.module('myApp.directives', ['myApp.filters'])
}
})
.directive('myMapPoint', function(ExternalResourcesManager) {
.directive('myGeoPointMap', function(ExternalResourcesManager) {
return {
link: link,
scope: {
point: '='
point: '=myGeoPointMap'
}
};
function link ($scope, element, attrs) {
var width = element.attr('width') || 200;
var height = element.attr('height') || 200;
element.attr('src', 'img/blank.gif');
var apiKey = 'AIzaSyC32ij28dCa0YzEV_HqbWfIwTZQql-RNS0';
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=13&size=200x100&scale=2&key=' + apiKey;
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) {
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']) @@ -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 audioVolume = 0.5;
@ -2628,7 +2646,8 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -2628,7 +2646,8 @@ angular.module('myApp.directives', ['myApp.filters'])
return {
link: link,
scope: {
audio: '='
audio: '=',
message: '='
},
templateUrl: templateUrl('audio_player')
};
@ -2710,6 +2729,12 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -2710,6 +2729,12 @@ angular.module('myApp.directives', ['myApp.filters'])
checkPlayer($scope.mediaPlayer.player);
$scope.mediaPlayer.player.setVolume(audioVolume);
$scope.mediaPlayer.player.play();
if ($scope.message &&
!$scope.message.out &&
$scope.message.media_unread) {
AppMessagesManager.readMessages([$scope.message.id]);
}
}, 300);
});
})
@ -2896,6 +2921,23 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -2896,6 +2921,23 @@ angular.module('myApp.directives', ['myApp.filters'])
};
})
.directive('myCopyField', function () {
return {
link: link
};
function link($scope, element, attrs) {
element.attr('readonly', 'true');
// element.on('keydown paste', cancelEvent);
element.on('click', function () {
this.select();
});
element[0].readonly = true;
};
})
.directive('mySubmitOnEnter', function () {
return {

10
app/js/lib/config.js

File diff suppressed because one or more lines are too long

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

@ -23,13 +23,13 @@ inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_na @@ -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;
inputMediaEmpty#9664f57f = InputMedia;
inputMediaUploadedPhoto#2dc53a7d file:InputFile = InputMedia;
inputMediaPhoto#8f2ab2ec id:InputPhoto = InputMedia;
inputMediaUploadedPhoto#f7aff1c0 file:InputFile caption:string = InputMedia;
inputMediaPhoto#e9bfb4f3 id:InputPhoto caption:string = InputMedia;
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = 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;
inputMediaUploadedThumbVideo#9912dabf file:InputFile thumb:InputFile duration:int w:int h:int mime_type:string = InputMedia;
inputMediaVideo#7f023ae6 id:InputVideo = InputMedia;
inputMediaUploadedVideo#e13fd4bc file:InputFile duration:int w:int h:int caption:string = InputMedia;
inputMediaUploadedThumbVideo#96fb97dc file:InputFile thumb:InputFile duration:int w:int h:int caption:string = InputMedia;
inputMediaVideo#936a4ebd id:InputVideo caption:string = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#94254732 file:InputFile crop:InputPhotoCrop = InputChatPhoto;
@ -87,7 +87,7 @@ chatEmpty#9ba2d800 id:int = Chat; @@ -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;
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;
@ -102,8 +102,8 @@ message#a7ab1991 flags:# id:int from_id:int to_id:Peer fwd_from_id:flags.2?int f @@ -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;
messageMediaEmpty#3ded6320 = MessageMedia;
messageMediaPhoto#c8c45a2a photo:Photo = MessageMedia;
messageMediaVideo#a2d24290 video:Video = MessageMedia;
messageMediaPhoto#3d8ce53d photo:Photo caption:string = MessageMedia;
messageMediaVideo#5bcf1675 video:Video caption:string = MessageMedia;
messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia;
messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia;
messageMediaUnsupported#9f84f49e = MessageMedia;
@ -119,14 +119,14 @@ messageActionChatDeleteUser#b2ae9b0c user_id:int = MessageAction; @@ -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;
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;
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;
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;
geoPoint#2049d70c long:double lat:double = GeoPoint;
@ -209,7 +209,6 @@ inputMessagesFilterAudio#cfc87522 = MessagesFilter; @@ -209,7 +209,6 @@ inputMessagesFilterAudio#cfc87522 = MessagesFilter;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = 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;
updateUserTyping#5c486927 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; @@ -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;
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;
@ -356,11 +355,11 @@ auth.sentAppCode#e325edcf phone_registered:Bool phone_code_hash:string send_call @@ -356,11 +355,11 @@ auth.sentAppCode#e325edcf phone_registered:Bool phone_code_hash:string send_call
sendMessageTypingAction#16bf744e = SendMessageAction;
sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
sendMessageRecordVideoAction#a187d66f = SendMessageAction;
sendMessageUploadVideoAction#92042ff7 = SendMessageAction;
sendMessageUploadVideoAction#e9763aec progress:int = SendMessageAction;
sendMessageRecordAudioAction#d52f73f7 = SendMessageAction;
sendMessageUploadAudioAction#e6ac8a6f = SendMessageAction;
sendMessageUploadPhotoAction#990a3c1a = SendMessageAction;
sendMessageUploadDocumentAction#8faee98e = SendMessageAction;
sendMessageUploadAudioAction#f351d7ab progress:int = SendMessageAction;
sendMessageUploadPhotoAction#d1d34a26 progress:int = SendMessageAction;
sendMessageUploadDocumentAction#aa0cd9e4 progress:int = SendMessageAction;
sendMessageGeoLocationAction#176f8ba1 = SendMessageAction;
sendMessageChooseContactAction#628cbc6f = SendMessageAction;
@ -450,6 +449,22 @@ account.passwordInputSettings#bcfc532c flags:# new_salt:flags.0?bytes new_passwo @@ -450,6 +449,22 @@ account.passwordInputSettings#bcfc532c flags:# new_salt:flags.0?bytes new_passwo
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---
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 @@ -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.deleteHistory#f4f8fb61 peer:InputPeer offset:int = messages.AffectedHistory;
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.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;
@ -600,4 +615,10 @@ account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings @@ -600,4 +615,10 @@ account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings
auth.checkPassword#a63011e password_hash:bytes = auth.Authorization;
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;

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

@ -23,6 +23,7 @@ @@ -23,6 +23,7 @@
"group_modal_menu_delete_chat": "Delete chat",
"group_modal_settings": "Settings",
"group_modal_notifications": "Notifications",
"group_modal_menu_share_link": "Invite to group via link",
"group_modal_members": "Members",
"group_modal_members_kick": "Remove",
@ -160,6 +161,12 @@ @@ -160,6 +161,12 @@
"group_edit_submit": "Save",
"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_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!",

135
app/js/services.js

@ -599,6 +599,64 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -599,6 +599,64 @@ 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 openInviteLink (hash) {
return MtpApiManager.invokeApi('messages.checkChatInvite', {
hash: hash
}).then(function (chatInvite) {
var chatTitle;
if (chatInvite._ == 'chatInviteAlready') {
saveApiChat(chatInvite.chat);
if (!chatInvite.chat.left) {
return $rootScope.$broadcast('history_focus', {
peerString: 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 hasChat (id) {
return angular.isObject(chats[id]);
}
@ -628,10 +686,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -628,10 +686,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
if (chatFull.participants && chatFull.participants._ == 'chatParticipants') {
MtpApiManager.getUserID().then(function (myID) {
chatFull.isAdmin = (myID == chatFull.participants.admin_id);
angular.forEach(chatFull.participants.participants, function(participant){
participant.user = AppUsersManager.getUser(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 +776,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -717,6 +776,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
getChatFull: getChatFull,
getChatPhoto: getChatPhoto,
getChatString: getChatString,
getChatInviteLink: getChatInviteLink,
openInviteLink: openInviteLink,
hasChat: hasChat,
wrapForFull: wrapForFull,
openChat: openChat
@ -792,7 +853,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -792,7 +853,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, StatusManager, _) {
var messagesStorage = {};
var messagesForHistory = {};
@ -1358,6 +1419,22 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1358,6 +1419,22 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
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) {
// console.log('start flush');
var peerID = AppPeersManager.getPeerID(inputPeer),
@ -1382,6 +1459,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1382,6 +1459,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
angular.forEach(apiMessages, function (apiMessage) {
apiMessage.unread = apiMessage.flags & 1 ? true : false;
apiMessage.out = apiMessage.flags & 2 ? true : false;
apiMessage.media_unread = apiMessage.flags & 32 ? true : false;
messagesStorage[apiMessage.id] = apiMessage;
apiMessage.date -= serverTimeOffset;
@ -1949,9 +2027,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1949,9 +2027,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
if (message.media) {
if (message.media.caption &&
message.media.caption.length) {
message.media.rCaption = RichTextProcessor.wrapRichText(message.media.caption);
}
switch (message.media._) {
case 'messageMediaPhoto':
message.media.photo = AppPhotosManager.wrapForHistory(message.media.photo.id)
message.media.photo = AppPhotosManager.wrapForHistory(message.media.photo.id);
break;
case 'messageMediaVideo':
@ -1966,6 +2049,22 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1966,6 +2049,22 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
message.media.audio = AppAudioManager.wrapForHistory(message.media.audio.id);
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':
message.media.rFullName = RichTextProcessor.wrapRichText(
message.media.first_name + ' ' + (message.media.last_name || ''),
@ -1975,7 +2074,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1975,7 +2074,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
case 'messageMediaWebPage':
if (!message.media.webpage ||
message.media.webpage._ == 'webPageEmpty') {
message.media.webpage._ == 'webPageEmpty' ||
Config.Mobile) {
delete message.media;
break;
}
@ -2187,7 +2287,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -2187,7 +2287,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
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;
default: notificationMessage = _('conversation_media_attachment_raw'); break;
}
@ -2464,6 +2565,21 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -2464,6 +2565,21 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
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':
var dialogsUpdated = {},
historiesUpdated = {},
@ -2558,6 +2674,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -2558,6 +2674,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
getSearch: getSearch,
getMessage: getMessage,
readHistory: readHistory,
readMessages: readMessages,
flushHistory: flushHistory,
deleteMessages: deleteMessages,
saveMessages: saveMessages,
@ -5152,7 +5269,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -5152,7 +5269,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
})
.service('LocationParamsService', function ($rootScope, $routeParams, AppUsersManager) {
.service('LocationParamsService', function ($rootScope, $routeParams, AppUsersManager, AppChatsManager) {
function checkTgAddr () {
if (!$routeParams.tgaddr) {
@ -5165,6 +5282,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -5165,6 +5282,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
peerString: AppUsersManager.getUserString(userID)
});
});
return;
}
var matches = $routeParams.tgaddr.match(/^(web\+)?tg:(\/\/)?join\?invite=(.+)$/);
if (matches && matches[3]) {
AppChatsManager.openInviteLink(matches[3]);
}
}

45
app/less/app.less

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

16
app/less/mobile.less

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

1
app/partials/desktop/audio_player.html

@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
<span ng-switch-when="true" ng-bind="::audio.file_name"></span>
<span ng-switch-default my-i18n="message_attach_audio_message"></span>
</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">
<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>

23
app/partials/desktop/chat_invite_link_modal.html

@ -0,0 +1,23 @@ @@ -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></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="updateLink(true)" 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 @@ @@ -51,6 +51,15 @@
</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">
<i class="md_modal_section_icon md_modal_section_icon_notification"></i>

1
app/partials/desktop/dialog.html

@ -70,6 +70,7 @@ @@ -70,6 +70,7 @@
</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="messageMediaVenue" my-i18n="conversation_media_location"></span>
<span ng-switch-when="messageMediaContact" my-i18n="conversation_media_contact"></span>
</span>

9
app/partials/desktop/message.html

@ -54,11 +54,12 @@ @@ -54,11 +54,12 @@
<!-- <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-switch-when="messageMediaPhoto" my-message-photo></div>
<div ng-switch-when="messageMediaVideo" my-message-video="historyMessage.media.video" message-id="historyMessage.id"></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" 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="messageMediaGeo" my-message-map></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-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="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>

9
app/partials/desktop/message_attach_geo.html

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

7
app/partials/desktop/message_attach_photo.html

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
<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)">
<img
class="im_message_photo_thumb"
my-load-thumb
thumb="historyMessage.media.photo.thumb"
/>
</a>

20
app/partials/desktop/message_attach_venue.html

@ -0,0 +1,20 @@ @@ -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 @@ @@ -1,34 +1,35 @@
<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'}">
<span class="im_message_video_duration" ng-bind="::video.duration | duration"></span>
<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="::media.video.duration | duration"></span>
<i class="icon icon-videoplay"></i>
<img
class="im_message_video_thumb im_message_video_thumb_blurred"
my-load-thumb
thumb="video.thumb"
thumb="media.video.thumb"
/>
</a>
<div class="im_message_document_info">
<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_size" ng-if="!video.progress.enabled" ng-bind="::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.size | formatSize"></span>
<span class="im_message_document_size" ng-if="media.video.progress.enabled" ng-bind="media.video.progress | formatSizeProgress"></span>
</div>
<div class="im_message_document_actions" ng-if="!video.progress.enabled">
<a href="" ng-click="videoSave()" ng-switch="video.downloaded">
<div class="im_message_document_actions" ng-if="!media.video.progress.enabled">
<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-default my-i18n="message_attach_video_download"></span>
</a>
<a href="" ng-click="videoOpen()" my-i18n="message_attach_video_play"></a>
</div>
<div class="clearfix im_message_cancelable_progress_wrap" ng-if="video.progress.enabled">
<a class="im_message_media_progress_cancel pull-right" ng-click="video.progress.cancel()" my-i18n="modal_cancel"></a>
<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="media.video.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap">
<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 ng-if="::media.rCaption" class="im_message_video_caption" ng-bind-html="::media.rCaption"></div>

1
app/partials/mobile/dialog.html

@ -77,6 +77,7 @@ @@ -77,6 +77,7 @@
</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="messageMediaVenue" my-i18n="conversation_media_location"></span>
<span ng-switch-when="messageMediaContact" my-i18n="conversation_media_contact"></span>
</span>

9
app/partials/mobile/message.html

@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
<span class="im_message_date" ng-bind="::historyMessage.date | time"></span>
</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>
@ -42,11 +42,12 @@ @@ -42,11 +42,12 @@
<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="messageMediaVideo" my-message-video="historyMessage.media.video" message-id="historyMessage.id"></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" 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="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="messageMediaPending" my-message-pending></div>
<div ng-switch-when="messageMediaUnsupported">

9
app/partials/mobile/message_attach_geo.html

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

7
app/partials/mobile/message_attach_photo.html

@ -1,7 +1,8 @@ @@ -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
class="im_message_photo_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>

9
app/partials/mobile/message_attach_video.html

@ -1,11 +1,12 @@ @@ -1,11 +1,12 @@
<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'}">
<span class="im_message_video_duration" ng-bind="::video.duration | duration"></span>
<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="::media.video.duration | duration"></span>
<i class="icon icon-videoplay"></i>
<img
class="im_message_video_thumb im_message_video_thumb_blurred"
my-load-thumb
thumb="video.thumb"
thumb="media.video.thumb"
/>
</a>
</div>
</div>
<div ng-if="::media.rCaption" class="im_message_video_caption" ng-bind-html="::media.rCaption"></div>
Loading…
Cancel
Save