Browse Source

Merge branch 'bots'

master
Igor Zhukov 10 years ago
parent
commit
84bb68479d
  1. BIN
      app/img/icons/General.png
  2. BIN
      app/img/icons/General_2x.png
  3. BIN
      app/img/icons/IconsetSmiles.png
  4. BIN
      app/img/icons/IconsetSmiles_2x.png
  5. 2
      app/index.html
  6. 364
      app/js/controllers.js
  7. 125
      app/js/directives.js
  8. 23
      app/js/filters.js
  9. 8
      app/js/lib/config.js
  10. 2
      app/js/lib/mtproto.js
  11. 86
      app/js/lib/mtproto_wrapper.js
  12. 136
      app/js/lib/ng_utils.js
  13. 67
      app/js/lib/schema.tl.txt
  14. 3
      app/js/lib/tl_utils.js
  15. 134
      app/js/lib/utils.js
  16. 11
      app/js/locales/en-us.json
  17. 289
      app/js/message_composer.js
  18. 714
      app/js/services.js
  19. 222
      app/less/app.less
  20. 301
      app/less/desktop.less
  21. 18
      app/less/mobile.less
  22. 8
      app/partials/desktop/audio_player.html
  23. 2
      app/partials/desktop/chat_modal.html
  24. 4
      app/partials/desktop/document_modal.html
  25. 29
      app/partials/desktop/im.html
  26. 12
      app/partials/desktop/message.html
  27. 2
      app/partials/desktop/message_attach_document.html
  28. 4
      app/partials/desktop/photo_modal.html
  29. 9
      app/partials/desktop/reply_markup.html
  30. 1
      app/partials/desktop/reply_message.html
  31. 27
      app/partials/desktop/user_modal.html
  32. 4
      app/partials/desktop/video_modal.html
  33. 8
      app/partials/mobile/audio_player.html
  34. 2
      app/partials/mobile/message_attach_document.html
  35. 2
      app/partials/mobile/photo_modal.html
  36. 6
      app/partials/mobile/user_modal.html
  37. 2
      app/partials/mobile/video_modal.html
  38. 1
      app/vendor/jquery.nanoscroller/nanoscroller.js
  39. 10321
      app/vendor/libwebpjs/libwebp-0.1.13.js
  40. 4079
      app/vendor/libwebpjs/libwebp-0.2.0.js
  41. 2
      app/webogram.appcache

BIN
app/img/icons/General.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
app/img/icons/General_2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
app/img/icons/IconsetSmiles.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
app/img/icons/IconsetSmiles_2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

2
app/index.html

@ -64,7 +64,7 @@
<script type="text/javascript" src="vendor/zlib/gunzip.min.js"></script> <script type="text/javascript" src="vendor/zlib/gunzip.min.js"></script>
<script type="text/javascript" src="vendor/closure/long.js"></script> <script type="text/javascript" src="vendor/closure/long.js"></script>
<script type="text/javascript" src="vendor/leemon_bigint/bigint.js"></script> <script type="text/javascript" src="vendor/leemon_bigint/bigint.js"></script>
<script type="text/javascript" src="vendor/libwebpjs/libwebp-0.2.0.min.js"></script> <script type="text/javascript" src="vendor/libwebpjs/libwebp-0.2.0.js"></script>
<script type="text/javascript" src="js/lib/utils.js"></script> <script type="text/javascript" src="js/lib/utils.js"></script>

364
app/js/controllers.js

@ -160,7 +160,6 @@ angular.module('myApp.controllers', ['myApp.i18n'])
function saveAuth (result) { function saveAuth (result) {
MtpApiManager.setUserAuth(options.dcID, { MtpApiManager.setUserAuth(options.dcID, {
expires: result.expires,
id: result.user.id id: result.user.id
}); });
$timeout.cancel(callTimeout); $timeout.cancel(callTimeout);
@ -408,9 +407,12 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$on('$routeUpdate', updateCurDialog); $scope.$on('$routeUpdate', updateCurDialog);
var pendingParams = false;
$scope.$on('history_focus', function (e, peerData) { $scope.$on('history_focus', function (e, peerData) {
$modalStack.dismissAll(); $modalStack.dismissAll();
if (peerData.peerString == $scope.curDialog.peer && peerData.messageID == $scope.curDialog.messageID) { if (peerData.peerString == $scope.curDialog.peer &&
peerData.messageID == $scope.curDialog.messageID &&
!peerData.startParam) {
$scope.$broadcast(peerData.messageID ? 'ui_history_change_scroll' : 'ui_history_focus'); $scope.$broadcast(peerData.messageID ? 'ui_history_change_scroll' : 'ui_history_focus');
} else { } else {
var peerID = AppPeersManager.getPeerID(peerData.peerString); var peerID = AppPeersManager.getPeerID(peerData.peerString);
@ -421,7 +423,19 @@ angular.module('myApp.controllers', ['myApp.i18n'])
peer = '@' + username; peer = '@' + username;
} }
} }
$location.url('/im?p=' + peer + (peerData.messageID ? '&m=' + peerData.messageID : '')); if (peerData.messageID || peerData.startParam) {
pendingParams = {
messageID: peerData.messageID,
startParam: peerData.startParam
};
} else {
pendingParams = false;
}
if ($routeParams.p != peer) {
$location.url('/im?p=' + peer);
} else {
updateCurDialog();
}
} }
}); });
@ -569,21 +583,24 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} else { } else {
lastSearch = false; lastSearch = false;
} }
var addParams = pendingParams || {};
pendingParams = false;
addParams.messageID = parseInt(addParams.messageID) || false;
addParams.startParam = addParams.startParam || false;
if ($routeParams.p && $routeParams.p.charAt(0) == '@') { if ($routeParams.p && $routeParams.p.charAt(0) == '@') {
if ($scope.curDialog === undefined) { if ($scope.curDialog === undefined) {
$scope.curDialog = {}; $scope.curDialog = {};
} }
AppUsersManager.resolveUsername($routeParams.p.substr(1)).then(function (userID) { AppUsersManager.resolveUsername($routeParams.p.substr(1)).then(function (userID) {
$scope.curDialog = { $scope.curDialog = angular.extend({
peer: AppUsersManager.getUserString(userID), peer: AppUsersManager.getUserString(userID)
messageID: parseInt($routeParams.m) || false }, addParams);
};
}); });
} else { } else {
$scope.curDialog = { $scope.curDialog = angular.extend({
peer: $routeParams.p || false, peer: $routeParams.p || false
messageID: parseInt($routeParams.m) || false }, addParams);
};
} }
} }
@ -639,6 +656,9 @@ angular.module('myApp.controllers', ['myApp.i18n'])
var topMessages = []; var topMessages = [];
var topToDialogs = {}; var topToDialogs = {};
angular.forEach(dialogsUpdated, function (dialog, peerID) { angular.forEach(dialogsUpdated, function (dialog, peerID) {
if ($scope.noUsers && peerID > 0) {
return;
}
topToDialogs[dialog.top_message] = dialog; topToDialogs[dialog.top_message] = dialog;
topMessages.push(dialog.top_message); topMessages.push(dialog.top_message);
}); });
@ -738,8 +758,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$location.url( $location.url(
'/im' + '/im' +
($scope.curDialog.peer ($scope.curDialog.peer
? '?p=' + $scope.curDialog.peer + ? '?p=' + $scope.curDialog.peer
($scope.curDialog.messageID ? '&m=' + $scope.curDialog.messageID : '')
: '' : ''
) )
); );
@ -777,7 +796,11 @@ angular.module('myApp.controllers', ['myApp.i18n'])
return AppMessagesManager.getSearch({_: 'inputPeerEmpty'}, $scope.search.query, {_: 'inputMessagesFilterEmpty'}, maxID); return AppMessagesManager.getSearch({_: 'inputPeerEmpty'}, $scope.search.query, {_: 'inputMessagesFilterEmpty'}, maxID);
}); });
} else { } else {
promise = AppMessagesManager.getDialogs($scope.search.query, maxID); var query = $scope.search.query;
if ($scope.noUsers) {
query = '%pg ' + (query || '');
}
promise = AppMessagesManager.getDialogs(query, maxID);
} }
return promise.then(function (result) { return promise.then(function (result) {
@ -862,7 +885,10 @@ angular.module('myApp.controllers', ['myApp.i18n'])
return; return;
} }
if (!hasMore && !searchMessages && ($scope.search.query || !$scope.dialogs.length)) { if (!hasMore &&
!searchMessages &&
!$scope.noUsers &&
($scope.search.query || !$scope.dialogs.length)) {
showMoreConversations(); showMoreConversations();
return; return;
} }
@ -937,7 +963,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}, 500); }, 500);
} }
if ($scope.search.query) { if ($scope.search.query && !$scope.noMessages) {
searchMessages = true; searchMessages = true;
loadDialogs(); loadDialogs();
} }
@ -947,7 +973,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
.controller('AppImHistoryController', function ($scope, $location, $timeout, $rootScope, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager, PeersSelectService, IdleManager, StatusManager, ErrorService) { .controller('AppImHistoryController', function ($scope, $location, $timeout, $rootScope, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager, PeersSelectService, IdleManager, StatusManager, ErrorService) {
$scope.$watch('curDialog', applyDialogSelect); $scope.$watchCollection('curDialog', applyDialogSelect);
ApiUpdatesManager.attach(); ApiUpdatesManager.attach();
IdleManager.start(); IdleManager.start();
@ -967,6 +993,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.selectedReply = selectedReply; $scope.selectedReply = selectedReply;
$scope.selectedCancel = selectedCancel; $scope.selectedCancel = selectedCancel;
$scope.selectedFlush = selectedFlush; $scope.selectedFlush = selectedFlush;
$scope.botStart = botStart;
$scope.toggleEdit = toggleEdit; $scope.toggleEdit = toggleEdit;
$scope.toggleMedia = toggleMedia; $scope.toggleMedia = toggleMedia;
@ -1007,8 +1034,9 @@ angular.module('myApp.controllers', ['myApp.i18n'])
var newPeer = newDialog.peer || $scope.curDialog.peer || ''; var newPeer = newDialog.peer || $scope.curDialog.peer || '';
peerID = AppPeersManager.getPeerID(newPeer); peerID = AppPeersManager.getPeerID(newPeer);
if (peerID == $scope.curDialog.peerID &&
if (peerID == $scope.curDialog.peerID && oldDialog.messageID == newDialog.messageID) { oldDialog.messageID == newDialog.messageID &&
oldDialog.startParam == newDialog.startParam) {
return false; return false;
} }
@ -1017,9 +1045,12 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.curDialog.inputPeer = AppPeersManager.getInputPeer(newPeer); $scope.curDialog.inputPeer = AppPeersManager.getInputPeer(newPeer);
$scope.historyFilter.mediaType = false; $scope.historyFilter.mediaType = false;
updateStartBot();
selectedCancel(true); selectedCancel(true);
if (oldDialog.peer && oldDialog.peer == newDialog.peer && newDialog.messageID) { if (oldDialog.peer &&
oldDialog.peer == newDialog.peer &&
newDialog.messageID) {
messageFocusHistory(); messageFocusHistory();
} }
else if (peerID) { else if (peerID) {
@ -1108,6 +1139,36 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
} }
function updateStartBot () {
var wasStartBot = $scope.historyState.startBot;
if (!peerID ||
peerID < 0 ||
!AppUsersManager.isBot(peerID) ||
$scope.historyFilter.mediaType ||
$scope.curDialog.messageID) {
$scope.historyState.startBot = false;
}
else if (
$scope.state.empty || (
peerHistory &&
peerHistory.messages.length == 1 &&
peerHistory.messages[0].action &&
peerHistory.messages[0].action._ == 'messageActionBotIntro'
)
) {
$scope.historyState.startBot = 2;
}
else if ($scope.curDialog.startParam) {
$scope.historyState.startBot = 1;
}
else {
$scope.historyState.startBot = false;
}
if (wasStartBot != $scope.historyState.startBot) {
$scope.$broadcast('ui_panel_update');
}
}
function messageFocusHistory () { function messageFocusHistory () {
var history = historiesQueueFind(peerID); var history = historiesQueueFind(peerID);
@ -1310,6 +1371,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
AppMessagesManager.readHistory($scope.curDialog.inputPeer); AppMessagesManager.readHistory($scope.curDialog.inputPeer);
updateStartBot();
}, function () { }, function () {
safeReplaceObject($scope.state, {error: true}); safeReplaceObject($scope.state, {error: true});
}); });
@ -1325,16 +1388,47 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$broadcast('ui_history_change'); $scope.$broadcast('ui_history_change');
} }
function botStart () {
AppMessagesManager.startBot(peerID, 0, $scope.curDialog.startParam);
$scope.curDialog.startParam = false;
}
function toggleMessage (messageID, $event) { function toggleMessage (messageID, $event) {
var target = $event.target, if ($scope.historyState.startBot) {
shiftClick = $event.shiftKey; return false;
}
if (shiftClick) { if (!$scope.historyState.selectActions) {
$scope.$broadcast('ui_selection_clear'); var sel = (
window.getSelection && window.getSelection() ||
document.getSelection && document.getSelection() ||
document.selection && document.selection.createRange().text || ''
).toString().replace(/^\s+|\s+$/g, '');
if (sel) {
return false;
}
var target = $event.target;
while (target) {
if (target.className.indexOf('im_message_outer_wrap') != -1) {
break;
}
if (target.tagName == 'A' ||
target.onclick ||
target.getAttribute('ng-click')) {
return false;
}
var events = $._data(target, 'events');
if (events && (events.click || events.mousedown)) {
return false;
}
target = target.parentNode;
}
} }
if (!$scope.historyState.selectActions && !$(target).hasClass('icon-select-tick') && !$(target).hasClass('im_content_message_select_area')) { var shiftClick = $event.shiftKey;
return false; if (shiftClick) {
$scope.$broadcast('ui_selection_clear');
} }
if ($scope.selectedMsgs[messageID]) { if ($scope.selectedMsgs[messageID]) {
@ -1382,6 +1476,11 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
function selectedCancel (noBroadcast) { function selectedCancel (noBroadcast) {
if (!noBroadcast &&
$scope.curDialog.startParam) {
delete $scope.curDialog.startParam;
return;
}
$scope.selectedMsgs = {}; $scope.selectedMsgs = {};
$scope.selectedCount = 0; $scope.selectedCount = 0;
$scope.historyState.selectActions = false; $scope.historyState.selectActions = false;
@ -1416,7 +1515,6 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
} }
function selectedForward () { function selectedForward () {
if ($scope.selectedCount > 0) { if ($scope.selectedCount > 0) {
var selectedMessageIDs = []; var selectedMessageIDs = [];
@ -1493,6 +1591,16 @@ angular.module('myApp.controllers', ['myApp.i18n'])
loadAfterSync = false; loadAfterSync = false;
}); });
$scope.$on('reply_button_press', function (e, button) {
var replyKeyboard = $scope.historyState.replyKeyboard;
if (!replyKeyboard) {
return;
}
AppMessagesManager.sendText(peerID, button.text, {
replyToMsgID: peerID < 0 && replyKeyboard.id
});
});
var typingTimeouts = {}; var typingTimeouts = {};
$scope.$on('history_append', function (e, addedMessage) { $scope.$on('history_append', function (e, addedMessage) {
@ -1552,6 +1660,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
AppMessagesManager.readHistory($scope.curDialog.inputPeer); AppMessagesManager.readHistory($scope.curDialog.inputPeer);
}); });
} }
updateStartBot();
} }
}); });
@ -1644,6 +1754,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
AppMessagesManager.readHistory($scope.curDialog.inputPeer); AppMessagesManager.readHistory($scope.curDialog.inputPeer);
}); });
} }
updateStartBot();
} }
}); });
@ -1673,6 +1785,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$broadcast('messages_regroup'); $scope.$broadcast('messages_regroup');
if (historyUpdate.peerID == $scope.curDialog.peerID) { if (historyUpdate.peerID == $scope.curDialog.peerID) {
$scope.state.empty = !newMessages.length; $scope.state.empty = !newMessages.length;
updateStartBot();
} }
}); });
@ -1683,6 +1796,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
history.ids = []; history.ids = [];
if (dialog.peerID == $scope.curDialog.peerID) { if (dialog.peerID == $scope.curDialog.peerID) {
$scope.state.empty = true; $scope.state.empty = true;
updateStartBot();
} }
} }
}); });
@ -1737,7 +1851,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$on('user_update', angular.noop); $scope.$on('user_update', angular.noop);
}) })
.controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, MtpApiFileManager) { .controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, MtpApiFileManager, RichTextProcessor) {
$scope.$watch('curDialog.peer', resetDraft); $scope.$watch('curDialog.peer', resetDraft);
$scope.$on('user_update', angular.noop); $scope.$on('user_update', angular.noop);
@ -1746,11 +1860,28 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}); });
$scope.$on('ui_typing', onTyping); $scope.$on('ui_typing', onTyping);
$scope.draftMessage = {text: '', send: sendMessage, replyClear: replyClear}; $scope.draftMessage = {
text: '',
send: sendMessage,
replyClear: replyClear
};
$scope.mentions = {}; $scope.mentions = {};
$scope.commands = {};
$scope.$watch('draftMessage.text', onMessageChange); $scope.$watch('draftMessage.text', onMessageChange);
$scope.$watch('draftMessage.files', onFilesSelected); $scope.$watch('draftMessage.files', onFilesSelected);
$scope.$watch('draftMessage.sticker', onStickerSelected); $scope.$watch('draftMessage.sticker', onStickerSelected);
$scope.$watch('draftMessage.command', onCommandSelected);
$scope.$on('history_reply_markup', function (e, peerData) {
if (peerData.peerID == $scope.curDialog.peerID) {
updateReplyKeyboard();
}
});
$scope.replyKeyboardToggle = replyKeyboardToggle;
$scope.toggleSlash = toggleSlash;
var replyToMarkup = false;
function sendMessage (e) { function sendMessage (e) {
$scope.$broadcast('ui_message_before_send'); $scope.$broadcast('ui_message_before_send');
@ -1822,9 +1953,53 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}); });
} }
function updateCommands () {
var peerID = $scope.curDialog.peerID;
AppProfileManager.getPeerBots(peerID).then(function (peerBots) {
if (!peerBots.length) {
safeReplaceObject($scope.commands, {});
$scope.$broadcast('mentions_update');
return;
}
var needMentions = peerBots.length > 1;
var commandsList = [];
var commandsIndex = SearchIndexManager.createIndex();
angular.forEach(peerBots, function (peerBot) {
var mention = '';
if (needMentions) {
var bot = AppUsersManager.getUser(peerBot.id);
if (bot && bot.username) {
mention += '@' + bot.username;
}
}
var botSearchText = AppUsersManager.getUserSearchText(peerBot.id);
angular.forEach(peerBot.commands, function (description, command) {
var value = '/' + command + mention;
commandsList.push({
botID: peerBot.id,
value: value,
rDescription: RichTextProcessor.wrapRichText(description, {noLinks: true, noLineBreaks: true})
});
SearchIndexManager.indexObject(value, botSearchText + ' ' + command + ' ' + description, commandsIndex);
})
});
safeReplaceObject($scope.commands, {
list: commandsList,
index: commandsIndex
});
$scope.$broadcast('mentions_update');
});
}
function resetDraft (newPeer) { function resetDraft (newPeer) {
updateMentions(); updateMentions();
updateCommands();
replyClear(); replyClear();
updateReplyKeyboard();
if (newPeer) { if (newPeer) {
Storage.get('draft' + $scope.curDialog.peerID).then(function (draftText) { Storage.get('draft' + $scope.curDialog.peerID).then(function (draftText) {
@ -1843,13 +2018,67 @@ angular.module('myApp.controllers', ['myApp.i18n'])
function replySelect(messageID) { function replySelect(messageID) {
$scope.draftMessage.replyToMessage = AppMessagesManager.wrapForDialog(messageID); $scope.draftMessage.replyToMessage = AppMessagesManager.wrapForDialog(messageID);
$scope.$broadcast('ui_peer_reply'); $scope.$broadcast('ui_peer_reply');
replyToMarkup = false;
} }
function replyClear() { function replyClear() {
var message = $scope.draftMessage.replyToMessage;
if (message &&
$scope.historyState.replyKeyboard &&
$scope.historyState.replyKeyboard.id == message.id &&
!$scope.historyState.replyKeyboard.pFlags.hidden) {
$scope.historyState.replyKeyboard.pFlags.hidden = true;
$scope.$broadcast('ui_keyboard_update');
}
delete $scope.draftMessage.replyToMessage; delete $scope.draftMessage.replyToMessage;
$scope.$broadcast('ui_peer_reply'); $scope.$broadcast('ui_peer_reply');
} }
function toggleSlash ($event) {
if ($scope.draftMessage.text &&
$scope.draftMessage.text.charAt(0) == '/') {
$scope.draftMessage.text = '';
} else {
$scope.draftMessage.text = '/';
}
$scope.$broadcast('ui_peer_draft');
return cancelEvent($event);
}
function updateReplyKeyboard () {
var peerID = $scope.curDialog.peerID;
var replyKeyboard = AppMessagesManager.getReplyKeyboard(peerID);
if (replyKeyboard) {
replyKeyboard = AppMessagesManager.wrapReplyMarkup(replyKeyboard);
}
// console.log('update reply markup', peerID, replyKeyboard);
$scope.historyState.replyKeyboard = replyKeyboard;
var addReplyMessage =
replyKeyboard &&
!replyKeyboard.hidden &&
(replyKeyboard._ == 'replyKeyboardForceReply' ||
(replyKeyboard._ == 'replyKeyboardMarkup' && peerID < 0));
if (addReplyMessage) {
replySelect(replyKeyboard.id);
replyToMarkup = true;
}
else if (replyToMarkup) {
replyClear();
}
$scope.$broadcast('ui_keyboard_update');
}
function replyKeyboardToggle ($event) {
var replyKeyboard = $scope.historyState.replyKeyboard;
if (replyKeyboard) {
replyKeyboard.pFlags.hidden = !replyKeyboard.pFlags.hidden;
updateReplyKeyboard();
}
return cancelEvent($event);
}
function onMessageChange(newVal) { function onMessageChange(newVal) {
// console.log('ctrl text changed', newVal); // console.log('ctrl text changed', newVal);
// console.trace('ctrl text changed', newVal); // console.trace('ctrl text changed', newVal);
@ -1916,6 +2145,17 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
delete $scope.draftMessage.sticker; delete $scope.draftMessage.sticker;
} }
function onCommandSelected (command) {
if (!command) {
return;
}
AppMessagesManager.sendText($scope.curDialog.peerID, command);
delete $scope.draftMessage.sticker;
delete $scope.draftMessage.text;
$scope.$broadcast('ui_message_send');
$scope.$broadcast('ui_peer_draft');
}
}) })
.controller('AppLangSelectController', function ($scope, _, Storage, ErrorService, AppRuntimeManager) { .controller('AppLangSelectController', function ($scope, _, Storage, ErrorService, AppRuntimeManager) {
@ -2156,8 +2396,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.nav = {}; $scope.nav = {};
$scope.canForward = true; $scope.canForward = true;
var inputUser = AppUsersManager.getUserInput($scope.userID), var list = [$scope.photoID],
list = [$scope.photoID],
maxID = $scope.photoID, maxID = $scope.photoID,
preloaded = {}, preloaded = {},
myID = 0, myID = 0,
@ -2165,7 +2404,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
updatePrevNext(); updatePrevNext();
AppPhotosManager.getUserPhotos(inputUser, 0, 1000).then(function (userpicCachedResult) { AppPhotosManager.getUserPhotos($scope.userID, 0, 1000).then(function (userpicCachedResult) {
if (userpicCachedResult.photos.indexOf($scope.photoID) >= 0) { if (userpicCachedResult.photos.indexOf($scope.photoID) >= 0) {
list = userpicCachedResult.photos; list = userpicCachedResult.photos;
maxID = list[list.length - 1]; maxID = list[list.length - 1];
@ -2234,7 +2473,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
function loadMore () { function loadMore () {
if (loadingPromise) return loadingPromise; if (loadingPromise) return loadingPromise;
return loadingPromise = AppPhotosManager.getUserPhotos(inputUser, maxID).then(function (userpicResult) { return loadingPromise = AppPhotosManager.getUserPhotos($scope.userID, maxID).then(function (userpicResult) {
if (userpicResult.photos.length) { if (userpicResult.photos.length) {
maxID = userpicResult.photos[userpicResult.photos.length - 1]; maxID = userpicResult.photos[userpicResult.photos.length - 1];
list = list.concat(userpicResult.photos); list = list.concat(userpicResult.photos);
@ -2503,7 +2742,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}) })
.controller('UserModalController', function ($scope, $location, $rootScope, $modal, AppUsersManager, MtpApiManager, NotificationsManager, AppPhotosManager, AppMessagesManager, AppPeersManager, PeersSelectService, ErrorService) { .controller('UserModalController', function ($scope, $location, $rootScope, AppProfileManager, $modal, AppUsersManager, MtpApiManager, NotificationsManager, AppPhotosManager, AppMessagesManager, AppPeersManager, PeersSelectService, ErrorService) {
var peerString = AppUsersManager.getUserString($scope.userID); var peerString = AppUsersManager.getUserString($scope.userID);
@ -2513,23 +2752,10 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.settings = {notifications: true}; $scope.settings = {notifications: true};
MtpApiManager.invokeApi('users.getFullUser', { AppProfileManager.getProfile($scope.userID, $scope.override).then(function (userFull) {
id: AppUsersManager.getUserInput($scope.userID) $scope.blocked = userFull.blocked;
}).then(function (userFullResult) { $scope.bot_info = userFull.bot_info;
if ($scope.override && $scope.override.phone_number) {
userFullResult.user.phone = $scope.override.phone_number;
if ($scope.override.first_name || $scope.override.last_name) {
userFullResult.user.first_name = $scope.override.first_name;
userFullResult.user.last_name = $scope.override.last_name;
}
AppUsersManager.saveApiUser(userFullResult.user);
} else {
AppUsersManager.saveApiUser(userFullResult.user, true);
}
AppPhotosManager.savePhoto(userFullResult.profile_photo);
$scope.blocked = userFullResult.blocked;
NotificationsManager.savePeerSettings($scope.userID, userFullResult.notify_settings);
NotificationsManager.getPeerMuted($scope.userID).then(function (muted) { NotificationsManager.getPeerMuted($scope.userID).then(function (muted) {
$scope.settings.notifications = !muted; $scope.settings.notifications = !muted;
@ -2538,11 +2764,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
return false; return false;
} }
NotificationsManager.getPeerSettings($scope.userID).then(function (settings) { NotificationsManager.getPeerSettings($scope.userID).then(function (settings) {
if (newValue) { settings.mute_until = newValue ? 0 : 2000000000;
settings.mute_until = 0;
} else {
settings.mute_until = 2000000000;
}
NotificationsManager.updatePeerSettings($scope.userID, settings); NotificationsManager.updatePeerSettings($scope.userID, settings);
}); });
}); });
@ -2588,6 +2810,26 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}); });
}; };
$scope.inviteToGroup = function () {
PeersSelectService.selectPeer({
confirm_type: 'INVITE_TO_GROUP',
noUsers: true
}).then(function (peerString) {
var peerID = AppPeersManager.getPeerID(peerString);
var chatID = peerID < 0 ? -peerID : 0;
AppMessagesManager.startBot($scope.user.id, chatID).then(function () {
$rootScope.$broadcast('history_focus', {peerString: peerString});
});
});
};
$scope.sendCommand = function (command) {
AppMessagesManager.sendText($scope.userID, '/' + command);
$rootScope.$broadcast('history_focus', {
peerString: peerString
});
};
$scope.toggleBlock = function (block) { $scope.toggleBlock = function (block) {
MtpApiManager.invokeApi(block ? 'contacts.block' : 'contacts.unblock', { MtpApiManager.invokeApi(block ? 'contacts.block' : 'contacts.unblock', {
id: AppUsersManager.getUserInput($scope.userID) id: AppUsersManager.getUserInput($scope.userID)
@ -2690,11 +2932,9 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}; };
$scope.kickFromGroup = function (userID) { $scope.kickFromGroup = function (userID) {
var user = AppUsersManager.getUser(userID);
MtpApiManager.invokeApi('messages.deleteChatUser', { MtpApiManager.invokeApi('messages.deleteChatUser', {
chat_id: $scope.chatID, chat_id: $scope.chatID,
user_id: {_: 'inputUserForeign', user_id: userID, access_hash: user.access_hash || '0'} user_id: AppUsersManager.getUserInput(userID)
}).then(onChatUpdated); }).then(onChatUpdated);
}; };
@ -2783,7 +3023,9 @@ angular.module('myApp.controllers', ['myApp.i18n'])
id: {_: 'inputUserSelf'} id: {_: 'inputUserSelf'}
}).then(function (userFullResult) { }).then(function (userFullResult) {
AppUsersManager.saveApiUser(userFullResult.user); AppUsersManager.saveApiUser(userFullResult.user);
AppPhotosManager.savePhoto(userFullResult.profile_photo); AppPhotosManager.savePhoto(userFullResult.profile_photo, {
user_id: userFullResult.user.id
});
}); });
$scope.notify = {volume: 0.5}; $scope.notify = {volume: 0.5};
@ -2854,8 +3096,10 @@ angular.module('myApp.controllers', ['myApp.i18n'])
crop: {_: 'inputPhotoCropAuto'} crop: {_: 'inputPhotoCropAuto'}
}).then(function (updateResult) { }).then(function (updateResult) {
AppUsersManager.saveApiUsers(updateResult.users); AppUsersManager.saveApiUsers(updateResult.users);
AppPhotosManager.savePhoto(updateResult.photo);
MtpApiManager.getUserID().then(function (id) { MtpApiManager.getUserID().then(function (id) {
AppPhotosManager.savePhoto(updateResult.photo, {
user_id: id
});
ApiUpdatesManager.processUpdateMessage({ ApiUpdatesManager.processUpdateMessage({
_: 'updateShort', _: 'updateShort',
update: { update: {

125
app/js/directives.js

@ -339,6 +339,16 @@ angular.module('myApp.directives', ['myApp.filters'])
}; };
function link ($scope, element, attrs) { function link ($scope, element, attrs) {
if (attrs.watch) {
$scope.$watch('replyMessage', function () {
checkMessage($scope, element);
});
} else {
checkMessage($scope, element);
}
}
function checkMessage ($scope, element) {
var message = $scope.replyMessage; var message = $scope.replyMessage;
if (!message.loading) { if (!message.loading) {
updateMessage($scope, element); updateMessage($scope, element);
@ -395,7 +405,7 @@ angular.module('myApp.directives', ['myApp.filters'])
var peerID = AppMessagesManager.getMessagePeer(message); var peerID = AppMessagesManager.getMessagePeer(message);
var peerString = AppPeersManager.getPeerString(peerID); var peerString = AppPeersManager.getPeerString(peerID);
$rootScope.$broadcast('history_focus', {peerString: peerString, messageID: message.id}); $rootScope.$broadcast('history_focus', {peerString: peerString, messageID: message.id});
}) })
} }
@ -403,6 +413,41 @@ angular.module('myApp.directives', ['myApp.filters'])
}) })
.directive('myReplyMarkup', function() {
return {
templateUrl: templateUrl('reply_markup'),
scope: {
'replyMarkup': '=myReplyMarkup'
},
link: link
};
function link ($scope, element, attrs) {
var scrollable = $('.reply_markup', element);
var scroller = new Scroller(scrollable, {
classPrefix: 'reply_markup',
maxHeight: 170
});
$scope.buttonSend = function (button) {
$scope.$emit('reply_button_press', button);
}
$scope.$on('ui_keyboard_update', function () {
onContentLoaded(function () {
scroller.updateHeight();
scroller.scrollTo(0);
$scope.$emit('ui_panel_update');
})
});
onContentLoaded(function () {
scroller.updateHeight();
$scope.$emit('ui_panel_update');
});
}
})
.directive('myMessagePhoto', function(AppPhotosManager) { .directive('myMessagePhoto', function(AppPhotosManager) {
return { return {
scope: { scope: {
@ -563,7 +608,7 @@ angular.module('myApp.directives', ['myApp.filters'])
onContentLoaded(function () { onContentLoaded(function () {
var selectedDialog = $(scrollableWrap).find('.active a.im_dialog')[0]; var selectedDialog = $(scrollableWrap).find('.active a.im_dialog')[0];
if (selectedDialog) { if (selectedDialog) {
scrollToDialog(selectedDialog.parentNode); scrollToNode(scrollableWrap, selectedDialog.parentNode, dialogsWrap);
} }
}); });
}); });
@ -621,7 +666,7 @@ angular.module('myApp.directives', ['myApp.filters'])
if (nextDialogWrap) { if (nextDialogWrap) {
$(nextDialogWrap).find('a').trigger('mousedown'); $(nextDialogWrap).find('a').trigger('mousedown');
scrollToDialog(nextDialogWrap); scrollToNode(scrollableWrap, nextDialogWrap, dialogsWrap);
} }
return cancelEvent(e); return cancelEvent(e);
@ -681,29 +726,13 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
if (nextDialogWrap) { if (nextDialogWrap) {
scrollToDialog(nextDialogWrap); scrollToNode(scrollableWrap, nextDialogWrap, dialogsWrap);
} }
return cancelEvent(e); return cancelEvent(e);
} }
} }
function scrollToDialog(dialogWrap) {
var elTop = dialogWrap.offsetTop - 15,
elHeight = dialogWrap.offsetHeight + 30,
scrollTop = scrollableWrap.scrollTop,
viewportHeight = scrollableWrap.clientHeight;
if (scrollTop > elTop) { // we are below the dialog to scroll
scrollableWrap.scrollTop = elTop;
$(dialogsWrap).nanoScroller({flash: true});
}
else if (scrollTop < elTop + elHeight - viewportHeight) { // we are over the dialog to scroll
scrollableWrap.scrollTop = elTop + elHeight - viewportHeight;
$(dialogsWrap).nanoScroller({flash: true});
}
}
} }
@ -1295,7 +1324,8 @@ angular.module('myApp.directives', ['myApp.filters'])
link: link, link: link,
scope: { scope: {
draftMessage: '=', draftMessage: '=',
mentions: '=' mentions: '=',
commands: '='
} }
}; };
@ -1363,8 +1393,8 @@ angular.module('myApp.directives', ['myApp.filters'])
getSendOnEnter: function () { getSendOnEnter: function () {
return sendOnEnter; return sendOnEnter;
}, },
getPeerImage: function (element, peerID) { getPeerImage: function (element, peerID, noReplace) {
if (cachedPeerPhotos[peerID]) { if (cachedPeerPhotos[peerID] && !noReplace) {
element.replaceWith(cachedPeerPhotos[peerID]); element.replaceWith(cachedPeerPhotos[peerID]);
return; return;
} }
@ -1376,8 +1406,14 @@ angular.module('myApp.directives', ['myApp.filters'])
}); });
}, },
mentions: $scope.mentions, mentions: $scope.mentions,
commands: $scope.commands,
onMessageSubmit: onMessageSubmit, onMessageSubmit: onMessageSubmit,
onFilePaste: onFilePaste onFilePaste: onFilePaste,
onCommandSend: function (command) {
$scope.$apply(function () {
$scope.draftMessage.command = command;
});
}
}); });
var richTextarea = composer.richTextareaEl[0]; var richTextarea = composer.richTextareaEl[0];
@ -1465,6 +1501,9 @@ angular.module('myApp.directives', ['myApp.filters'])
if (!Config.Navigator.touch) { if (!Config.Navigator.touch) {
composer.focus(); composer.focus();
} }
onContentLoaded(function () {
composer.checkAutocomplete(true);
});
if (emojiTooltip) { if (emojiTooltip) {
emojiTooltip.hide(); emojiTooltip.hide();
} }
@ -1838,8 +1877,10 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
if ($scope.document.url) { if ($scope.document.url) {
$scope.isActive = !$scope.isActive; onContentLoaded(function () {
$scope.$emit('ui_height'); $scope.isActive = !$scope.isActive;
$scope.$emit('ui_height');
})
return; return;
} }
@ -1859,7 +1900,7 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
}) })
.directive('myLoadSticker', function(MtpApiFileManager, FileManager) { .directive('myLoadSticker', function(MtpApiFileManager, FileManager, AppStickersManager) {
var emptySrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; var emptySrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
@ -1876,13 +1917,7 @@ angular.module('myApp.directives', ['myApp.filters'])
.addClass(attrs.imgClass); .addClass(attrs.imgClass);
var setSrc = function (blob) { var setSrc = function (blob) {
if (WebpManager.isWebpSupported()) { imgElement.attr('src', FileManager.getUrl(blob));
imgElement.attr('src', FileManager.getUrl(blob, 'image/webp'));
return;
}
FileManager.getByteArray(blob).then(function (bytes) {
imgElement.attr('src', WebpManager.getPngUrlFromData(bytes));
});
}; };
imgElement.css({ imgElement.css({
@ -1894,12 +1929,16 @@ angular.module('myApp.directives', ['myApp.filters'])
height: $scope.document.thumb.height height: $scope.document.thumb.height
}); });
var smallLocation = $scope.document.thumb.location; var smallLocation = angular.copy($scope.document.thumb.location);
smallLocation.sticker = true;
var fullLocation = { var fullLocation = {
_: 'inputDocumentFileLocation', _: 'inputDocumentFileLocation',
id: $scope.document.id, id: $scope.document.id,
access_hash: $scope.document.access_hash, access_hash: $scope.document.access_hash,
dc_id: $scope.document.dc_id dc_id: $scope.document.dc_id,
file_name: $scope.document.file_name,
sticker: true
}; };
@ -1932,6 +1971,14 @@ angular.module('myApp.directives', ['myApp.filters'])
console.log('Download sticker failed', e, fullLocation); console.log('Download sticker failed', e, fullLocation);
}); });
} }
if (attrs.open && $scope.document.stickerSetInput) {
element
.addClass('clickable')
.on('click', function () {
AppStickersManager.openStickerset($scope.document.stickerSetInput);
});
}
} }
}) })
@ -2415,8 +2462,8 @@ angular.module('myApp.directives', ['myApp.filters'])
update = function () { update = function () {
var user = AppUsersManager.getUser(userID); var user = AppUsersManager.getUser(userID);
element element
.html(statusFilter(user)) .html(statusFilter(user, attrs.botChatPrivacy))
.toggleClass('status_online', user.status && user.status._ == 'userStatusOnline'); .toggleClass('status_online', user.status && user.status._ == 'userStatusOnline' || false);
}; };
$scope.$watch(attrs.myUserStatus, function (newUserID) { $scope.$watch(attrs.myUserStatus, function (newUserID) {
@ -2606,7 +2653,6 @@ angular.module('myApp.directives', ['myApp.filters'])
}; };
function link($scope, element, attrs) { function link($scope, element, attrs) {
element.addClass('peer_photo_init'); element.addClass('peer_photo_init');
var peerID, peer, peerPhoto; var peerID, peer, peerPhoto;
@ -2682,6 +2728,7 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
$scope.$watch(attrs.myPeerPhotolink, setPeerID); $scope.$watch(attrs.myPeerPhotolink, setPeerID);
setPeerID($scope.$eval(attrs.myPeerPhotolink));
if (attrs.watch) { if (attrs.watch) {
$scope.$on('user_update', function (e, updUserID) { $scope.$on('user_update', function (e, updUserID) {

23
app/js/filters.js

@ -31,8 +31,11 @@ angular.module('myApp.filters', ['myApp.i18n'])
.filter('userStatus', function($filter, _) { .filter('userStatus', function($filter, _) {
var relativeTimeFilter = $filter('relativeTime'); var relativeTimeFilter = $filter('relativeTime');
return function (user) { return function (user, botChatPrivacy) {
var statusType = user && user.status && user.status._ || 'userStatusEmpty'; var statusType = user && user.status && user.status._;
if (!statusType) {
statusType = user.pFlags.bot ? 'userStatusBot' : 'userStatusEmpty';
}
switch (statusType) { switch (statusType) {
case 'userStatusOnline': case 'userStatusOnline':
return _('user_status_online'); return _('user_status_online');
@ -49,6 +52,16 @@ angular.module('myApp.filters', ['myApp.i18n'])
case 'userStatusLastMonth': case 'userStatusLastMonth':
return _('user_status_last_month'); return _('user_status_last_month');
case 'userStatusBot':
if (botChatPrivacy) {
if (user.pFlags.botNoPrivacy) {
return _('user_status_bot_noprivacy');
} else {
return _('user_status_bot_privacy');
}
}
return _('user_status_bot');
case 'userStatusEmpty': case 'userStatusEmpty':
default: default:
return _('user_status_long_ago'); return _('user_status_long_ago');
@ -69,7 +82,9 @@ angular.module('myApp.filters', ['myApp.i18n'])
var dateFilter = $filter('date'); var dateFilter = $filter('date');
return function (timestamp, extended) { return function (timestamp, extended) {
if (!timestamp) {
return '';
}
var ticks = timestamp * 1000, var ticks = timestamp * 1000,
diff = Math.abs(tsNow() - ticks), diff = Math.abs(tsNow() - ticks),
format = 'shortTime'; format = 'shortTime';
@ -157,7 +172,7 @@ angular.module('myApp.filters', ['myApp.i18n'])
return size + ' b'; return size + ' b';
} }
else if (size < 1048576) { else if (size < 1048576) {
return Math.round(size / 1024) + ' Kb'; return Math.round(size / 1024) + ' KB';
} }
var mbs = size / 1048576; var mbs = size / 1048576;
if (progressing) { if (progressing) {

8
app/js/lib/config.js

File diff suppressed because one or more lines are too long

2
app/js/lib/mtproto.js

@ -14,7 +14,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
? [ ? [
{id: 1, host: '149.154.175.10', port: 80}, {id: 1, host: '149.154.175.10', port: 80},
{id: 2, host: '149.154.167.40', port: 80}, {id: 2, host: '149.154.167.40', port: 80},
{id: 3, host: '174.140.142.5', port: 80} {id: 3, host: '149.154.175.117', port: 80}
] ]
: [ : [
{id: 1, host: '149.154.175.50', port: 80}, {id: 1, host: '149.154.175.50', port: 80},

86
app/js/lib/mtproto_wrapper.js

@ -255,7 +255,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
} }
}) })
.factory('MtpApiFileManager', function (MtpApiManager, $q, FileManager, IdbFileStorage, TmpfsFileStorage, MemoryFileStorage) { .factory('MtpApiFileManager', function (MtpApiManager, $q, qSync, FileManager, IdbFileStorage, TmpfsFileStorage, MemoryFileStorage, WebpManager) {
var cachedFs = false; var cachedFs = false;
var cachedFsPromise = false; var cachedFsPromise = false;
@ -318,17 +318,29 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
return 'video' + location.id + '.mp4'; return 'video' + location.id + '.mp4';
case 'inputDocumentFileLocation': case 'inputDocumentFileLocation':
var fileName = (location.file_name || '').split('.', 2);
var ext = fileName[1] || '';
if (location.sticker && !WebpManager.isWebpSupported()) {
ext += '.png';
}
if (fileName.length) {
return fileName[0] + '_' + location.id + '.' + ext;
}
return 'doc' + location.id; return 'doc' + location.id;
case 'inputAudioFileLocation': case 'inputAudioFileLocation':
return 'audio' + location.id; return 'audio' + location.id;
}
if (!location.volume_id) { default:
console.trace('Empty location', location); if (!location.volume_id) {
console.trace('Empty location', location);
}
var ext = 'jpg';
if (location.sticker) {
ext = WebpManager.isWebpSupported() ? 'webp' : 'png';
}
return location.volume_id + '_' + location.local_id + '_' + location.secret + '.' + ext;
} }
return location.volume_id + '_' + location.local_id + '_' + location.secret + '.jpg';
}; };
function getTempFileName(file) { function getTempFileName(file) {
@ -374,7 +386,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
} }
// console.log('dload small', location); // console.log('dload small', location);
var fileName = getFileName(location), var fileName = getFileName(location),
mimeType = 'image/jpeg', mimeType = location.sticker ? 'image/webp' : 'image/jpeg',
cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName]; cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName];
if (cachedPromise) { if (cachedPromise) {
@ -403,11 +415,20 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
}); });
}); });
var processDownloaded = function (bytes) {
if (!location.sticker || WebpManager.isWebpSupported()) {
return qSync.when(bytes);
}
return WebpManager.getPngBlobFromWebp(bytes);
};
return fileStorage.getFileWriter(fileName, mimeType).then(function (fileWriter) { return fileStorage.getFileWriter(fileName, mimeType).then(function (fileWriter) {
return downloadPromise.then(function (result) { return downloadPromise.then(function (result) {
return FileManager.write(fileWriter, result.bytes).then(function () { return processDownloaded(result.bytes).then(function (proccessedResult) {
return cachedDownloads[fileName] = fileWriter.finalize(); return FileManager.write(fileWriter, proccessedResult).then(function () {
}); return cachedDownloads[fileName] = fileWriter.finalize();
});
})
}); });
}); });
}); });
@ -427,6 +448,16 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
options = options || {}; options = options || {};
var processSticker = false;
if (location.sticker && !WebpManager.isWebpSupported()) {
if (options.toFileEntry || size > 524288) {
delete location.sticker;
} else {
processSticker = true;
options.mime = 'image/png';
}
}
// console.log(dT(), 'Dload file', dcID, location, size); // console.log(dT(), 'Dload file', dcID, location, size);
var fileName = getFileName(location), var fileName = getFileName(location),
toFileEntry = options.toFileEntry || null, toFileEntry = options.toFileEntry || null,
@ -471,6 +502,13 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
}, function () { }, function () {
var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType); var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
var processDownloaded = function (bytes) {
if (!processSticker) {
return qSync.when(bytes);
}
return WebpManager.getPngBlobFromWebp(bytes);
};
fileWriterPromise.then(function (fileWriter) { fileWriterPromise.then(function (fileWriter) {
cacheFileWriter = fileWriter; cacheFileWriter = fileWriter;
var limit = 524288, var limit = 524288,
@ -513,20 +551,22 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
if (canceled) { if (canceled) {
return $q.when(); return $q.when();
} }
return FileManager.write(fileWriter, result.bytes).then(function () { return processDownloaded(result.bytes).then(function (processedResult) {
writeFileDeferred.resolve(); return FileManager.write(fileWriter, processedResult).then(function () {
}, errorHandler).then(function () { writeFileDeferred.resolve();
if (isFinal) { }, errorHandler).then(function () {
resolved = true; if (isFinal) {
if (toFileEntry) { resolved = true;
deferred.resolve(); if (toFileEntry) {
deferred.resolve();
} else {
deferred.resolve(cachedDownloads[fileName] = fileWriter.finalize());
}
} else { } else {
deferred.resolve(cachedDownloads[fileName] = fileWriter.finalize()); deferred.notify({done: offset + limit, total: size});
} };
} else { });
deferred.notify({done: offset + limit, total: size}); })
};
});
}); });
}); });
})(offset + limit >= size, offset, writeFileDeferred, writeFilePromise); })(offset + limit >= size, offset, writeFileDeferred, writeFilePromise);

136
app/js/lib/ng_utils.js

@ -43,7 +43,9 @@ angular.module('izhukov.utils', [])
}, },
reject: function (result) { reject: function (result) {
return {then: function (cb, badcb) { return {then: function (cb, badcb) {
return badcb(result); if (badcb) {
return badcb(result);
}
}}; }};
} }
} }
@ -205,6 +207,19 @@ angular.module('izhukov.utils', [])
return $q.reject(e); return $q.reject(e);
} }
} }
else if (fileData.file) {
var deferred = $q.defer();
fileData.file(function (blob) {
getByteArray(blob).then(function (result) {
deferred.resolve(result);
}, function (error) {
deferred.reject(error);
})
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
}
return $q.when(fileData); return $q.when(fileData);
} }
@ -408,6 +423,10 @@ angular.module('izhukov.utils', [])
return saveFileBase64(db, fileName, blob); return saveFileBase64(db, fileName, blob);
} }
if (!(blob instanceof Blob)) {
blob = blobConstruct([blob]);
}
try { try {
var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName), var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName),
request = objectStore.put(blob, fileName); request = objectStore.put(blob, fileName);
@ -643,6 +662,121 @@ angular.module('izhukov.utils', [])
}; };
}) })
.service('WebpManager', function (qSync, $q) {
var nativeWebpSupport = false;
var image = new Image();
image.onload = function () {
nativeWebpSupport = this.width === 2 && this.height === 1;
};
image.onerror = function () {
nativeWebpSupport = false;
};
image.src = 'data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA==';
var canvas, context;
function getCanvasFromWebp(data) {
var start = tsNow();
var decoder = new WebPDecoder();
var config = decoder.WebPDecoderConfig;
var buffer = config.j || config.output;
var bitstream = config.input;
if (!decoder.WebPInitDecoderConfig(config)) {
console.error('[webpjs] Library version mismatch!');
return false;
}
// console.log('[webpjs] status code', decoder.VP8StatusCode);
var StatusCode = decoder.VP8StatusCode;
status = decoder.WebPGetFeatures(data, data.length, bitstream);
if (status != (StatusCode.VP8_STATUS_OK || 0)) {
console.error('[webpjs] status error', status, StatusCode);
}
var mode = decoder.WEBP_CSP_MODE;
buffer.colorspace = mode.MODE_RGBA;
buffer.J = 4;
try {
status = decoder.WebPDecode(data, data.length, config);
} catch (e) {
status = e;
}
ok = (status == 0);
if (!ok) {
console.error('[webpjs] decoding failed', status, StatusCode);
return false;
}
// console.log('[webpjs] decoded: ', buffer.width, buffer.height, bitstream.has_alpha, 'Now saving...');
var bitmap = buffer.c.RGBA.ma;
// console.log('[webpjs] done in ', tsNow() - start);
if (!bitmap) {
return false;
}
var biHeight = buffer.height;
var biWidth = buffer.width;
if (!canvas || !context) {
canvas = document.createElement('canvas');
context = canvas.getContext('2d');
} else {
context.clearRect(0, 0, canvas.width, canvas.height);
}
canvas.height = biHeight;
canvas.width = biWidth;
var output = context.createImageData(canvas.width, canvas.height);
var outputData = output.data;
for (var h = 0; h < biHeight; h++) {
for (var w = 0; w < biWidth; w++) {
outputData[0+w*4+(biWidth*4)*h] = bitmap[1+w*4+(biWidth*4)*h];
outputData[1+w*4+(biWidth*4)*h] = bitmap[2+w*4+(biWidth*4)*h];
outputData[2+w*4+(biWidth*4)*h] = bitmap[3+w*4+(biWidth*4)*h];
outputData[3+w*4+(biWidth*4)*h] = bitmap[0+w*4+(biWidth*4)*h];
};
}
context.putImageData(output, 0, 0);
return true;
}
function getPngBlobFromWebp (data) {
if (!getCanvasFromWebp(data)) {
return $q.reject({type: 'WEBP_PROCESS_FAILED'});
}
if (canvas.toBlob === undefined) {
return qSync.when(dataUrlToBlob(canvas.toDataURL('image/png')));
}
var deferred = $q.defer();
canvas.toBlob(function (blob) {
deferred.resolve(blob);
}, 'image/png');
return deferred.promise;
}
return {
isWebpSupported: function () {
return nativeWebpSupport;
},
getPngBlobFromWebp: getPngBlobFromWebp
}
})
.service('CryptoWorker', function ($timeout, $q) { .service('CryptoWorker', function ($timeout, $q) {
var webWorker = false, var webWorker = false,

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

@ -9,14 +9,10 @@ null#56730bcc = Null;
inputPeerEmpty#7f3b18ea = InputPeer; inputPeerEmpty#7f3b18ea = InputPeer;
inputPeerSelf#7da07ec9 = InputPeer; inputPeerSelf#7da07ec9 = InputPeer;
inputPeerContact#1023dbe8 user_id:int = InputPeer;
inputPeerForeign#9b447325 user_id:int access_hash:long = InputPeer;
inputPeerChat#179be863 chat_id:int = InputPeer; inputPeerChat#179be863 chat_id:int = InputPeer;
inputUserEmpty#b98886cf = InputUser; inputUserEmpty#b98886cf = InputUser;
inputUserSelf#f7c1b13f = InputUser; inputUserSelf#f7c1b13f = InputUser;
inputUserContact#86e94f65 user_id:int = InputUser;
inputUserForeign#655e74ff user_id:int access_hash:long = InputUser;
inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_name:string = InputContact; inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_name:string = InputContact;
@ -70,11 +66,6 @@ fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileL
fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation; fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation;
userEmpty#200250ba id:int = User; userEmpty#200250ba id:int = User;
userSelf#1c60e608 id:int first_name:string last_name:string username:string phone:string photo:UserProfilePhoto status:UserStatus = User;
userContact#cab35e18 id:int first_name:string last_name:string username:string access_hash:long phone:string photo:UserProfilePhoto status:UserStatus = User;
userRequest#d9ccc4ef id:int first_name:string last_name:string username:string access_hash:long phone:string photo:UserProfilePhoto status:UserStatus = User;
userForeign#75cf7a8 id:int first_name:string last_name:string username:string access_hash:long photo:UserProfilePhoto status:UserStatus = User;
userDeleted#d6016d7a id:int first_name:string last_name:string username:string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto; userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto;
@ -87,7 +78,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#cade0791 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite = ChatFull; chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
@ -98,7 +89,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto; chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto;
messageEmpty#83e5de54 id:int = Message; messageEmpty#83e5de54 id:int = Message;
message#a7ab1991 flags:# id:int from_id:int to_id:Peer fwd_from_id:flags.2?int fwd_date:flags.2?int reply_to_msg_id:flags.3?int date:int message:string media:MessageMedia = Message; message#c3060325 flags:# id:int from_id:int to_id:Peer fwd_from_id:flags.2?int fwd_date:flags.2?int reply_to_msg_id:flags.3?int date:int message:string media:MessageMedia reply_markup:flags.6?ReplyMarkup = Message;
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;
@ -119,14 +110,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#c3838076 id:long access_hash:long user_id:int date:int geo:GeoPoint sizes:Vector<PhotoSize> = Photo; photo#cded42fe id:long access_hash:long date:int 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#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; video#f72887d3 id:long access_hash:long date:int duration:int mime_type:string 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;
@ -135,7 +126,7 @@ auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone;
auth.sentCode#efed51d9 phone_registered:Bool phone_code_hash:string send_call_timeout:int is_password:Bool = auth.SentCode; auth.sentCode#efed51d9 phone_registered:Bool phone_code_hash:string send_call_timeout:int is_password:Bool = auth.SentCode;
auth.authorization#f6b673a4 expires:int user:User = auth.Authorization; auth.authorization#ff036af1 user:User = auth.Authorization;
auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization; auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization;
@ -157,7 +148,7 @@ peerNotifySettings#8d5e11ee mute_until:int sound:string show_previews:Bool event
wallPaper#ccb03657 id:int title:string sizes:Vector<PhotoSize> color:int = WallPaper; wallPaper#ccb03657 id:int title:string sizes:Vector<PhotoSize> color:int = WallPaper;
userFull#771095da user:User link:contacts.Link profile_photo:Photo notify_settings:PeerNotifySettings blocked:Bool real_first_name:string real_last_name:string = UserFull; userFull#5a89ac5b user:User link:contacts.Link profile_photo:Photo notify_settings:PeerNotifySettings blocked:Bool bot_info:BotInfo = UserFull;
contact#f911c994 user_id:int mutual:Bool = Contact; contact#f911c994 user_id:int mutual:Bool = Contact;
@ -206,6 +197,7 @@ inputMessagesFilterPhotoVideo#56e9f0e4 = MessagesFilter;
inputMessagesFilterPhotoVideoDocuments#d95e73bb = MessagesFilter; inputMessagesFilterPhotoVideoDocuments#d95e73bb = MessagesFilter;
inputMessagesFilterDocument#9eddf188 = MessagesFilter; inputMessagesFilterDocument#9eddf188 = MessagesFilter;
inputMessagesFilterAudio#cfc87522 = MessagesFilter; inputMessagesFilterAudio#cfc87522 = MessagesFilter;
inputMessagesFilterAudioDocuments#5afbf764 = 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;
@ -240,7 +232,7 @@ photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; 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#5d8c6cc flags:# id:int ip_address:string port:int = DcOption;
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; 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;
@ -335,7 +327,7 @@ inputAudioFileLocation#74dc404d id:long access_hash:long = InputFileLocation;
inputDocumentFileLocation#4e45abe9 id:long access_hash:long = InputFileLocation; inputDocumentFileLocation#4e45abe9 id:long access_hash:long = InputFileLocation;
audioEmpty#586988d8 id:long = Audio; audioEmpty#586988d8 id:long = Audio;
audio#c7ac6496 id:long access_hash:long user_id:int date:int duration:int mime_type:string size:int dc_id:int = Audio; audio#f9e35055 id:long access_hash:long date:int duration:int mime_type:string size:int dc_id:int = Audio;
documentEmpty#36f8c871 id:long = Document; documentEmpty#36f8c871 id:long = Document;
document#f9a39f4f id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector<DocumentAttribute> = Document; document#f9a39f4f id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector<DocumentAttribute> = Document;
@ -405,7 +397,7 @@ documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
documentAttributeAnimated#11b58939 = DocumentAttribute; documentAttributeAnimated#11b58939 = DocumentAttribute;
documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute; documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute;
documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute; documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute;
documentAttributeAudio#51448e5 duration:int = DocumentAttribute; documentAttributeAudio#ded218e0 duration:int title:string performer:string = DocumentAttribute;
documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickersNotModified#f1749a22 = messages.Stickers;
@ -414,7 +406,7 @@ messages.stickers#8a8ecd32 hash:string stickers:Vector<Document> = messages.Stic
stickerPack#12b299d4 emoticon:string documents:Vector<long> = StickerPack; stickerPack#12b299d4 emoticon:string documents:Vector<long> = StickerPack;
messages.allStickersNotModified#e86602c3 = messages.AllStickers; messages.allStickersNotModified#e86602c3 = messages.AllStickers;
messages.allStickers#5ce352ec hash:string packs:Vector<StickerPack> sets:Vector<StickerSet> documents:Vector<Document> = messages.AllStickers; messages.allStickers#d51dafdb hash:string sets:Vector<StickerSet> = messages.AllStickers;
disabledFeature#ae636f24 feature:string description:string = DisabledFeature; disabledFeature#ae636f24 feature:string description:string = DisabledFeature;
@ -469,10 +461,32 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet;
inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
stickerSet#a7a43b17 id:long access_hash:long title:string short_name:string = StickerSet; stickerSet#cd303b41 flags:# id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet;
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
user#22e49072 flags:# id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int = User;
botCommand#c27ac8c7 command:string description:string = BotCommand;
botInfoEmpty#bb2e37ce = BotInfo;
botInfo#9cf585d user_id:int version:int share_text:string description:string commands:Vector<BotCommand> = BotInfo;
keyboardButton#a2fa4880 text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyKeyboardHide#a03e5b85 flags:# = ReplyMarkup;
replyKeyboardForceReply#f4108aa0 flags:# = ReplyMarkup;
replyKeyboardMarkup#3502758c flags:# rows:Vector<KeyboardButtonRow> = ReplyMarkup;
inputPeerUser#7b8e7de6 user_id:int access_hash:long = InputPeer;
inputUser#d8292816 user_id:int access_hash:long = InputUser;
help.appChangelogEmpty#af7e0394 = help.AppChangelog;
help.appChangelog#4668e6bd text:string = help.AppChangelog;
---functions--- ---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -524,8 +538,8 @@ messages.deleteHistory#f4f8fb61 peer:InputPeer offset:int = messages.AffectedHis
messages.deleteMessages#a5f18925 id:Vector<int> = messages.AffectedMessages; messages.deleteMessages#a5f18925 id:Vector<int> = messages.AffectedMessages;
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>; 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#fc55e6b5 flags:# peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup = messages.SentMessage;
messages.sendMedia#2d7923b1 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long = Updates; messages.sendMedia#c8f16791 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long reply_markup:flags.2?ReplyMarkup = Updates;
messages.forwardMessages#55e1728d peer:InputPeer id:Vector<int> random_id:Vector<long> = Updates; messages.forwardMessages#55e1728d peer:InputPeer id:Vector<int> random_id:Vector<long> = Updates;
messages.getChats#3c6aa187 id:Vector<int> = messages.Chats; messages.getChats#3c6aa187 id:Vector<int> = messages.Chats;
messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull; messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull;
@ -551,7 +565,7 @@ help.getAppUpdate#c812ac7e device_model:string system_version:string app_version
help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool; help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
help.getInviteText#a4a95186 lang_code:string = help.InviteText; help.getInviteText#a4a95186 lang_code:string = help.InviteText;
photos.getUserPhotos#b7ee553c user_id:InputUser offset:int max_id:int limit:int = photos.Photos; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
messages.forwardMessage#33963bf9 peer:InputPeer id:int random_id:long = Updates; messages.forwardMessage#33963bf9 peer:InputPeer id:int random_id:long = Updates;
messages.sendBroadcast#bf73f4da contacts:Vector<InputUser> random_id:Vector<long> message:string media:InputMedia = Updates; messages.sendBroadcast#bf73f4da contacts:Vector<InputUser> random_id:Vector<long> message:string media:InputMedia = Updates;
@ -613,6 +627,8 @@ messages.getAllStickers#aa3bc868 hash:string = messages.AllStickers;
account.updateDeviceLocked#38df3532 period:int = Bool; account.updateDeviceLocked#38df3532 period:int = Bool;
auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization;
messages.getWebPagePreview#25223e24 message:string = MessageMedia; messages.getWebPagePreview#25223e24 message:string = MessageMedia;
account.getAuthorizations#e320c158 = account.Authorizations; account.getAuthorizations#e320c158 = account.Authorizations;
@ -631,5 +647,8 @@ messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite;
messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite;
messages.importChatInvite#6c50051c hash:string = Updates; messages.importChatInvite#6c50051c hash:string = Updates;
messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
messages.installStickerSet#efbbfae9 stickerset:InputStickerSet = Bool; messages.installStickerSet#7b30c3a6 stickerset:InputStickerSet disabled:Bool = Bool;
messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
messages.startBot#1b3e0ffc bot:InputUser chat_id:int random_id:long start_param:string = Updates;
help.getAppChangelog#5bab7fb2 device_model:string system_version:string app_version:string lang_code:string = help.AppChangelog;

3
app/js/lib/tl_utils.js

@ -102,6 +102,9 @@ TLSerialization.prototype.storeLong = function (sLong, field) {
} }
} }
if (typeof sLong != 'string') {
sLong = sLong ? sLong.toString() : '0';
}
var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000)); var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
this.writeInt(intToUint(divRem[1].intValue()), (field || '') + ':long[low]'); this.writeInt(intToUint(divRem[1].intValue()), (field || '') + ':long[low]');

134
app/js/lib/utils.js

@ -49,6 +49,21 @@ function cancelEvent (event) {
return false; return false;
} }
function getScrollWidth() {
var outer = $('<div>').css({
position: 'absolute',
width: 100,
height: 100,
overflow: 'scroll',
top: -9999
}).appendTo($(document.body));
var scrollbarWidth = outer[0].offsetWidth - outer[0].clientWidth;
outer.remove();
return scrollbarWidth;
};
function onCtrlEnter (textarea, cb) { function onCtrlEnter (textarea, cb) {
$(textarea).on('keydown', function (e) { $(textarea).on('keydown', function (e) {
if (e.keyCode == 13 && (e.ctrlKey || e.metaKey)) { if (e.keyCode == 13 && (e.ctrlKey || e.metaKey)) {
@ -222,6 +237,22 @@ function setRichFocus(field, selectNode) {
} }
} }
function scrollToNode (scrollable, node, scroller) {
var elTop = node.offsetTop - 15,
elHeight = node.offsetHeight + 30,
scrollTop = scrollable.scrollTop,
viewportHeight = scrollable.clientHeight;
if (scrollTop > elTop) { // we are below the node to scroll
scrollable.scrollTop = elTop;
$(scroller).nanoScroller({flash: true});
}
else if (scrollTop < elTop + elHeight - viewportHeight) { // we are over the node to scroll
scrollable.scrollTop = elTop + elHeight - viewportHeight;
$(scroller).nanoScroller({flash: true});
}
}
function onContentLoaded (cb) { function onContentLoaded (cb) {
setZeroTimeout(cb); setZeroTimeout(cb);
} }
@ -365,11 +396,15 @@ function versionCompare (ver1, ver2) {
} }
function cleanSearchText (text) { function cleanSearchText (text) {
var hasTag = text.charAt(0) == '%';
text = text.replace(badCharsRe, ' ').replace(trimRe, ''); text = text.replace(badCharsRe, ' ').replace(trimRe, '');
text = text.replace(/[^A-Za-z0-9]/g, function (ch) { text = text.replace(/[^A-Za-z0-9]/g, function (ch) {
return Config.LatinizeMap[ch] || ch; return Config.LatinizeMap[ch] || ch;
}); });
text = text.toLowerCase(); text = text.toLowerCase();
if (hasTag) {
text = '%' + text;
}
return text; return text;
} }
@ -451,102 +486,3 @@ function versionCompare (ver1, ver2) {
}; };
})(window); })(window);
(function (global) {
var nativeWebpSupport = false;
var image = new Image();
image.onload = function () {
nativeWebpSupport = this.width === 2 && this.height === 1;
};
image.onerror = function () {
nativeWebpSupport = false;
};
image.src = 'data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA==';
var canvas, context;
function getPngUrlFromData(data) {
var start = tsNow();
var decoder = new WebPDecoder();
var config = decoder.WebPDecoderConfig;
var buffer = config.j;
var bitstream = config.input;
if (!decoder.WebPInitDecoderConfig(config)) {
console.error('[webpjs] Library version mismatch!');
return false;
}
// console.log('[webpjs] status code', decoder.VP8StatusCode);
status = decoder.WebPGetFeatures(data, data.length, bitstream);
if (status != 0) {
console.error('[webpjs] status error', status);
}
var mode = decoder.WEBP_CSP_MODE;
buffer.J = 4;
try {
status = decoder.WebPDecode(data, data.length, config);
} catch (e) {
status = e;
}
ok = (status == 0);
if (!ok) {
console.error('[webpjs] decoding failed', status);
return false;
}
// console.log('[webpjs] decoded: ', buffer.width, buffer.height, bitstream.has_alpha, 'Now saving...');
var bitmap = buffer.c.RGBA.ma;
// console.log('[webpjs] done in ', tsNow() - start);
if (!bitmap) {
return false;
}
var biHeight = buffer.height;
var biWidth = buffer.width;
if (!canvas || !context) {
canvas = document.createElement('canvas');
context = canvas.getContext('2d');
} else {
context.clearRect(0, 0, canvas.width, canvas.height);
}
canvas.height = biHeight;
canvas.width = biWidth;
var output = context.createImageData(canvas.width, canvas.height);
var outputData = output.data;
for (var h = 0; h < biHeight; h++) {
for (var w = 0; w < biWidth; w++) {
outputData[0+w*4+(biWidth*4)*h] = bitmap[1+w*4+(biWidth*4)*h];
outputData[1+w*4+(biWidth*4)*h] = bitmap[2+w*4+(biWidth*4)*h];
outputData[2+w*4+(biWidth*4)*h] = bitmap[3+w*4+(biWidth*4)*h];
outputData[3+w*4+(biWidth*4)*h] = bitmap[0+w*4+(biWidth*4)*h];
};
}
context.putImageData(output, 0, 0);
return canvas.toDataURL('image/png');
}
global.WebpManager = {
isWebpSupported: function () {
return nativeWebpSupport;
},
getPngUrlFromData: getPngUrlFromData
}
})(window);

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

@ -115,8 +115,12 @@
"user_modal_block_user": "Block user", "user_modal_block_user": "Block user",
"user_modal_unblock_user": "Unblock user", "user_modal_unblock_user": "Unblock user",
"user_modal_delete_chat": "Delete chat", "user_modal_delete_chat": "Delete chat",
"user_modal_add_to_group": "Add to group",
"user_modal_info": "Info", "user_modal_info": "Info",
"user_modal_phone": "Phone", "user_modal_phone": "Phone",
"user_modal_about": "About",
"user_modal_bot_settings": "Settings",
"user_modal_bot_help": "Help",
"user_modal_username": "Username", "user_modal_username": "Username",
"user_modal_settings": "Settings", "user_modal_settings": "Settings",
"user_modal_notifications": "Notifications", "user_modal_notifications": "Notifications",
@ -144,6 +148,9 @@
"user_status_last_week": "last seen within a week", "user_status_last_week": "last seen within a week",
"user_status_last_month": "last seen within a month", "user_status_last_month": "last seen within a month",
"user_status_long_ago": "last seen a long time ago", "user_status_long_ago": "last seen a long time ago",
"user_status_bot": "bot",
"user_status_bot_noprivacy": "has access to messages",
"user_status_bot_privacy": "has no access to messages",
"chat_title_deleted": "DELETED", "chat_title_deleted": "DELETED",
"format_size_progress_mulitple": "{done} of {total} {parts}", "format_size_progress_mulitple": "{done} of {total} {parts}",
"format_size_progress": "{done} of {total}", "format_size_progress": "{done} of {total}",
@ -274,7 +281,8 @@
"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_joined_by_link": "joined group via invite link",
"message_service_unsupported_action": "Unsupported action {action}", "message_service_unsupported_action": "unsupported action {action}",
"message_service_bot_intro_header": "What can this bot do?",
"error_modal_warning_title": "Warning", "error_modal_warning_title": "Warning",
"error_modal_bad_request_title": "Error", "error_modal_bad_request_title": "Error",
@ -390,6 +398,7 @@
"im_delete": "Delete {count}", "im_delete": "Delete {count}",
"im_forward": "Forward {count}", "im_forward": "Forward {count}",
"im_reply": "Reply", "im_reply": "Reply",
"im_start": "Start",
"im_reply_loading": "Loading{dots}", "im_reply_loading": "Loading{dots}",
"im_photos_drop_text": "Drop photos here to send", "im_photos_drop_text": "Drop photos here to send",
"im_message_field_placeholder": "Write a message...", "im_message_field_placeholder": "Write a message...",

289
app/js/message_composer.js

@ -196,21 +196,6 @@ EmojiTooltip.prototype.onMouseLeave = function (triggerUnshow) {
} }
}; };
EmojiTooltip.prototype.getScrollWidth = function() {
var outer = $('<div>').css({
position: 'absolute',
width: 100,
height: 100,
overflow: 'scroll',
top: -9999
}).appendTo($(document.body));
var scrollbarWidth = outer[0].offsetWidth - outer[0].clientWidth;
outer.remove();
return scrollbarWidth;
};
EmojiTooltip.prototype.createTooltip = function () { EmojiTooltip.prototype.createTooltip = function () {
@ -219,20 +204,12 @@ EmojiTooltip.prototype.createTooltip = function () {
} }
var self = this; var self = this;
this.tooltipEl = $('<div class="composer_emoji_tooltip noselect"><div class="composer_emoji_tooltip_tabs"></div><div class="composer_emoji_tooltip_content_wrap nano mobile_scrollable_wrap"><div class="composer_emoji_tooltip_content nano-content clearfix"></div></div><div class="composer_emoji_tooltip_footer"><a class="composer_emoji_tooltip_settings"></a></div><div class="composer_emoji_tooltip_tail"><i class="icon icon-tooltip-tail"></i></div></div>').appendTo(document.body); this.tooltipEl = $('<div class="composer_emoji_tooltip noselect"><div class="composer_emoji_tooltip_tabs"></div><div class="composer_emoji_tooltip_content clearfix"></div><div class="composer_emoji_tooltip_footer"><a class="composer_emoji_tooltip_settings"></a></div><div class="composer_emoji_tooltip_tail"><i class="icon icon-tooltip-tail"></i></div></div>').appendTo(document.body);
this.tabsEl = $('.composer_emoji_tooltip_tabs', this.tooltip); this.tabsEl = $('.composer_emoji_tooltip_tabs', this.tooltipEl);
this.contentWrapEl = $('.composer_emoji_tooltip_content_wrap', this.tooltip); this.contentEl = $('.composer_emoji_tooltip_content', this.tooltipEl);
this.contentEl = $('.composer_emoji_tooltip_content', this.tooltip); this.footerEl = $('.composer_emoji_tooltip_footer', this.tooltipEl);
this.footerEl = $('.composer_emoji_tooltip_footer', this.tooltip); this.settingsEl = $('.composer_emoji_tooltip_settings', this.tooltipEl);
this.settingsEl = $('.composer_emoji_tooltip_settings', this.tooltip);
var scrollWidth = this.getScrollWidth();
if (scrollWidth > 0) {
this.tooltipEl.css({
width: parseInt(this.tooltipEl.css('width')) + scrollWidth
});
}
angular.forEach(['recent', 'smile', 'flower', 'bell', 'car', 'grid', 'stickers'], function (tabName, tabIndex) { angular.forEach(['recent', 'smile', 'flower', 'bell', 'car', 'grid', 'stickers'], function (tabName, tabIndex) {
var tab = $('<a class="composer_emoji_tooltip_tab composer_emoji_tooltip_tab_' + tabName + '"></a>') var tab = $('<a class="composer_emoji_tooltip_tab composer_emoji_tooltip_tab_' + tabName + '"></a>')
@ -254,9 +231,7 @@ EmojiTooltip.prototype.createTooltip = function () {
} }
}); });
if (!Config.Mobile) { this.scroller = new Scroller(this.contentEl, {classPrefix: 'composer_emoji_tooltip'});
this.contentWrapEl.nanoScroller({preventPageScrolling: true, tabIndex: -1});
}
this.contentEl.on('mousedown', function (e) { this.contentEl.on('mousedown', function (e) {
e = e.originalEvent || e; e = e.originalEvent || e;
@ -323,13 +298,7 @@ EmojiTooltip.prototype.updateTabContents = function () {
var renderContent = function () { var renderContent = function () {
self.contentEl.html(html.join('')); self.contentEl.html(html.join(''));
self.scroller.reinit();
if (!Config.Mobile) {
self.contentWrapEl.nanoScroller({scroll: 'top'});
setTimeout(function () {
self.contentWrapEl.nanoScroller();
}, 100);
}
} }
if (this.tab == 6) { // Stickers if (this.tab == 6) { // Stickers
@ -487,12 +456,15 @@ function MessageComposer (textarea, options) {
this.setUpInput(); this.setUpInput();
this.autoCompleteEl = $('<ul class="composer_dropdown dropdown-menu"></ul>').appendTo(document.body); this.autoCompleteWrapEl = $('<div class="composer_dropdown_wrap"></div>').appendTo(document.body);
this.autoCompleteEl = $('<ul class="composer_dropdown dropdown-menu"></ul>').appendTo(this.autoCompleteWrapEl);
this.scroller = new Scroller(this.autoCompleteEl, {maxHeight: 180});
var self = this; var self = this;
this.autoCompleteEl.on('mousedown', function (e) { this.autoCompleteEl.on('mousedown', function (e) {
e = e.originalEvent || e; e = e.originalEvent || e;
var target = $(e.target), mention, code; var target = $(e.target), mention, code, command;
if (target[0].tagName != 'A') { if (target[0].tagName != 'A') {
target = $(target[0].parentNode); target = $(target[0].parentNode);
} }
@ -503,9 +475,13 @@ function MessageComposer (textarea, options) {
EmojiHelper.pushPopularEmoji(code); EmojiHelper.pushPopularEmoji(code);
} }
if (mention = target.attr('data-mention')) { if (mention = target.attr('data-mention')) {
if (self.onMentionSelected) { self.onMentionSelected(mention);
self.onMentionSelected(mention); }
if (command = target.attr('data-command')) {
if (self.onCommandSelected) {
self.onCommandSelected(command);
} }
self.hideSuggestions();
} }
return cancelEvent(e); return cancelEvent(e);
}); });
@ -517,16 +493,25 @@ function MessageComposer (textarea, options) {
this.getSendOnEnter = options.getSendOnEnter; this.getSendOnEnter = options.getSendOnEnter;
this.onFilePaste = options.onFilePaste; this.onFilePaste = options.onFilePaste;
this.mentions = options.mentions; this.mentions = options.mentions;
this.commands = options.commands;
this.getPeerImage = options.getPeerImage; this.getPeerImage = options.getPeerImage;
this.onCommandSend = options.onCommandSend;
} }
MessageComposer.autoCompleteRegEx = /(\s|^)(:|@|\/)([A-Za-z0-9\-\+\*@_]*)$/;
MessageComposer.prototype.setUpInput = function () { MessageComposer.prototype.setUpInput = function () {
if ('contentEditable' in document.body) { if ('contentEditable' in document.body) {
this.setUpRich(); this.setUpRich();
} else { } else {
this.setUpPlaintext(); this.setUpPlaintext();
} }
this.autoCompleteRegEx = /(?:\s|^)(:|@)([A-Za-z0-9\-\+\*_]*)$/;
var sbWidth = getScrollWidth();
if (sbWidth) {
(this.richTextareaEl || this.textareaEl).css({marginRight: -sbWidth});
}
} }
MessageComposer.prototype.setUpRich = function () { MessageComposer.prototype.setUpRich = function () {
@ -602,12 +587,14 @@ MessageComposer.prototype.onKeyEvent = function (e) {
currentSelected.removeClass('composer_autocomplete_option_active'); currentSelected.removeClass('composer_autocomplete_option_active');
if (nextWrap) { if (nextWrap) {
$(nextWrap).find('a').addClass('composer_autocomplete_option_active'); $(nextWrap).find('a').addClass('composer_autocomplete_option_active');
this.scroller.scrollToNode(nextWrap);
return cancelEvent(e); return cancelEvent(e);
} }
} }
var childNodes = this.autoCompleteEl[0].childNodes; var childNodes = this.autoCompleteEl[0].childNodes;
var nextWrap = childNodes[next ? 0 : childNodes.length - 1]; var nextWrap = childNodes[next ? 0 : childNodes.length - 1];
this.scroller.scrollToNode(nextWrap);
$(nextWrap).find('a').addClass('composer_autocomplete_option_active'); $(nextWrap).find('a').addClass('composer_autocomplete_option_active');
return cancelEvent(e); return cancelEvent(e);
@ -618,17 +605,21 @@ MessageComposer.prototype.onKeyEvent = function (e) {
if (!currentSelected.length && e.keyCode == 9) { if (!currentSelected.length && e.keyCode == 9) {
currentSelected = $(this.autoCompleteEl[0].childNodes[0]).find('a'); currentSelected = $(this.autoCompleteEl[0].childNodes[0]).find('a');
} }
var code, mention; var code, mention, command;
if (code = currentSelected.attr('data-code')) { if (code = currentSelected.attr('data-code')) {
this.onEmojiSelected(code, true); this.onEmojiSelected(code, true);
EmojiHelper.pushPopularEmoji(code); EmojiHelper.pushPopularEmoji(code);
return cancelEvent(e); return cancelEvent(e);
} }
if (mention = currentSelected.attr('data-mention')) { if (mention = currentSelected.attr('data-mention')) {
if (this.onMentionSelected) { this.onMentionSelected(mention);
this.onMentionSelected(mention); return cancelEvent(e);
return cancelEvent(e); }
if (command = currentSelected.attr('data-command')) {
if (this.onCommandSelected) {
this.onCommandSelected(command, e.keyCode == 9);
} }
return cancelEvent(e);
} }
checkSubmit = true; checkSubmit = true;
} }
@ -692,7 +683,7 @@ MessageComposer.prototype.restoreSelection = function () {
MessageComposer.prototype.checkAutocomplete = function () { MessageComposer.prototype.checkAutocomplete = function (forceFull) {
if (Config.Mobile) { if (Config.Mobile) {
return false; return false;
} }
@ -708,17 +699,19 @@ MessageComposer.prototype.checkAutocomplete = function () {
var value = textarea.value; var value = textarea.value;
} }
value = value.substr(0, pos); if (!forceFull) {
value = value.substr(0, pos);
}
var matches = value.match(this.autoCompleteRegEx); var matches = value.match(MessageComposer.autoCompleteRegEx);
if (matches) { if (matches) {
if (this.previousQuery == matches[0]) { if (this.previousQuery == matches[0]) {
return; return;
} }
this.previousQuery = matches[0]; this.previousQuery = matches[0];
var query = SearchIndexManager.cleanSearchText(matches[2]); var query = SearchIndexManager.cleanSearchText(matches[3]);
if (matches[1] == '@') { // mentions if (matches[2] == '@') { // mentions
if (this.mentions && this.mentions.index) { if (this.mentions && this.mentions.index) {
if (query.length) { if (query.length) {
var foundObject = SearchIndexManager.search(query, this.mentions.index); var foundObject = SearchIndexManager.search(query, this.mentions.index);
@ -742,7 +735,31 @@ MessageComposer.prototype.checkAutocomplete = function () {
this.hideSuggestions(); this.hideSuggestions();
} }
} }
else { // emoji else if (!matches[1] && matches[2] == '/') { // commands
if (this.commands && this.commands.index) {
if (query.length) {
var foundObject = SearchIndexManager.search(query, this.commands.index);
var foundCommands = [];
var command;
for (var i = 0, length = this.commands.list.length; i < length; i++) {
command = this.commands.list[i];
if (foundObject[command.value]) {
foundCommands.push(command);
}
}
} else {
var foundCommands = this.commands.list;
}
if (foundCommands.length) {
this.showCommandsSuggestions(foundCommands);
} else {
this.hideSuggestions();
}
} else {
this.hideSuggestions();
}
}
else if (matches[2] == ':') { // emoji
EmojiHelper.getPopularEmoji((function (popular) { EmojiHelper.getPopularEmoji((function (popular) {
if (query.length) { if (query.length) {
var found = EmojiHelper.searchEmojis(query); var found = EmojiHelper.searchEmojis(query);
@ -977,6 +994,25 @@ MessageComposer.prototype.onMentionSelected = function (username) {
this.onChange(); this.onChange();
} }
MessageComposer.prototype.onCommandSelected = function (command, isTab) {
if (isTab) {
if (this.richTextareaEl) {
this.richTextareaEl.html(encodeEntities(command) + '&nbsp;');
setRichFocus(this.richTextareaEl[0]);
}
else {
var textarea = this.textareaEl[0];
textarea.value = command + ' ';
setFieldSelection(textarea);
}
} else {
this.onCommandSend(command);
}
this.hideSuggestions();
this.onChange();
}
MessageComposer.prototype.onChange = function (e) { MessageComposer.prototype.onChange = function (e) {
if (this.richTextareaEl) { if (this.richTextareaEl) {
delete this.keyupStarted; delete this.keyupStarted;
@ -1028,6 +1064,13 @@ MessageComposer.prototype.focus = function () {
} }
} }
MessageComposer.prototype.renderSuggestions = function (html) {
this.autoCompleteEl.html(html.join(''));
this.autoCompleteWrapEl.show();
this.scroller.reinit();
this.updatePosition();
this.autocompleteShown = true;
}
MessageComposer.prototype.showEmojiSuggestions = function (codes) { MessageComposer.prototype.showEmojiSuggestions = function (codes) {
var html = []; var html = [];
@ -1052,16 +1095,13 @@ MessageComposer.prototype.showEmojiSuggestions = function (codes) {
} }
} }
this.autoCompleteEl.html(html.join('')); this.renderSuggestions(html);
this.autoCompleteEl.show();
this.updatePosition();
this.autocompleteShown = true;
} }
MessageComposer.prototype.showMentionSuggestions = function (users) { MessageComposer.prototype.showMentionSuggestions = function (users) {
var html = []; var html = [];
var user; var user;
var count = Math.min(5, users.length); var count = users.length;
var i; var i;
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
@ -1069,27 +1109,53 @@ MessageComposer.prototype.showMentionSuggestions = function (users) {
html.push('<li><a class="composer_mention_option" data-mention="' + user.username + '"><span class="composer_user_photo" data-user-id="' + user.id + '"></span><span class="composer_user_name">' + user.rFullName + '</span><span class="composer_user_mention">@' + user.username + '</span></a></li>'); html.push('<li><a class="composer_mention_option" data-mention="' + user.username + '"><span class="composer_user_photo" data-user-id="' + user.id + '"></span><span class="composer_user_name">' + user.rFullName + '</span><span class="composer_user_mention">@' + user.username + '</span></a></li>');
} }
this.autoCompleteEl.html(html.join('')); this.renderSuggestions(html);
var self = this; var self = this;
this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) { this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) {
self.getPeerImage($(element), element.getAttribute('data-user-id')); self.getPeerImage($(element), element.getAttribute('data-user-id'));
}); });
}
this.autoCompleteEl.show(); MessageComposer.prototype.showCommandsSuggestions = function (commands) {
this.updatePosition(); var html = [];
this.autocompleteShown = true; var command;
var count = Math.min(200, commands.length);
var i;
for (i = 0; i < count; i++) {
command = commands[i];
html.push('<li><a class="composer_command_option" data-command="' + encodeEntities(command.value) + '"><span class="composer_user_photo" data-user-id="' + command.botID + '"></span><span class="composer_command_value">' + encodeEntities(command.value) + '</span><span class="composer_command_desc">' + command.rDescription + '</span></a></li>');
}
this.renderSuggestions(html);
var self = this;
var usedImages = {};
this.autoCompleteEl.find('.composer_user_photo').each(function (k, element) {
var noReplace = true;
var botID = element.getAttribute('data-user-id');
if (!usedImages[botID]) {
usedImages[botID] = true;
noReplace = false;
}
self.getPeerImage($(element), botID, noReplace);
});
} }
MessageComposer.prototype.updatePosition = function () { MessageComposer.prototype.updatePosition = function () {
var offset = (this.richTextareaEl || this.textareaEl).offset(); var offset = (this.richTextareaEl || this.textareaEl).offset();
var height = this.autoCompleteEl.outerHeight();
var width = (this.richTextareaEl || this.textareaEl).outerWidth(); var width = (this.richTextareaEl || this.textareaEl).outerWidth();
this.autoCompleteEl.css({top: offset.top - height, left: offset.left, width: width - 2}); var height = this.scroller.updateHeight();
this.autoCompleteWrapEl.css({
top: offset.top - height,
left: offset.left,
width: width - 2
});
this.scroller.update();
} }
MessageComposer.prototype.hideSuggestions = function () { MessageComposer.prototype.hideSuggestions = function () {
this.autoCompleteEl.hide(); this.autoCompleteWrapEl.hide();
delete this.autocompleteShown; delete this.autocompleteShown;
} }
@ -1098,3 +1164,92 @@ MessageComposer.prototype.resetTyping = function () {
this.lastLength = 0; this.lastLength = 0;
} }
function Scroller(content, options) {
options = options || {};
var classPrefix = options.classPrefix || 'scroller';
this.content = $(content);
this.content.wrap('<div class="' + classPrefix + '_scrollable_container"><div class="' + classPrefix + '_scrollable_wrap"><div class="' + classPrefix + '_scrollable"></div></div></div>');
this.scrollable = $(this.content[0].parentNode);
this.scroller = $(this.scrollable[0].parentNode);
this.wrap = $(this.scroller[0].parentNode);
this.useNano = options.nano !== undefined ? options.nano : !Config.Mobile;
this.maxHeight = options.maxHeight;
this.minHeight = options.minHeight;
if (this.useNano) {
this.scrollable.addClass('nano-content');
this.scroller.addClass('nano');
this.scroller.nanoScroller({preventPageScrolling: true, tabIndex: -1});
} else {
if (this.maxHeight) {
this.wrap.css({maxHeight: this.maxHeight});
}
if (this.minHeight) {
this.wrap.css({minHeight: this.minHeight});
}
}
this.updateHeight();
}
Scroller.prototype.update = function () {
if (this.useNano) {
$(this.scroller).nanoScroller();
}
}
Scroller.prototype.reinit = function () {
this.scrollTo(0);
if (this.useNano) {
setTimeout((function () {
this.updateHeight();
}).bind(this), 100)
}
}
Scroller.prototype.updateHeight = function () {
var height;
if (this.maxHeight || this.minHeight) {
height = this.content[0].offsetHeight;
if (this.maxHeight && height > this.maxHeight) {
height = this.maxHeight;
}
if (this.minHeight && height < this.minHeight) {
height = this.minHeight;
}
this.wrap.css({height: height});
} else {
height = this.scroller[0].offsetHeight;
}
$(this.scroller).nanoScroller();
return height;
}
Scroller.prototype.scrollTo = function (scrollTop) {
this.scrollable[0].scrollTop = scrollTop;
if (this.useNano) {
$(this.scroller).nanoScroller({flash: true});
}
}
Scroller.prototype.scrollToNode = function (node) {
node = node[0] || node;
var elTop = node.offsetTop - 15,
elHeight = node.offsetHeight + 30,
scrollTop = this.scrollable[0].scrollTop,
viewportHeight = this.scrollable[0].clientHeight;
if (scrollTop > elTop) { // we are below the node to scroll
this.scrollTo(elTop);
}
else if (scrollTop < elTop + elHeight - viewportHeight) { // we are over the node to scroll
this.scrollTo(elTop + elHeight - viewportHeight);
}
}

714
app/js/services.js

File diff suppressed because it is too large Load Diff

222
app/less/app.less

@ -858,7 +858,7 @@ a.tg_radio_on:hover i.icon-radio {
vertical-align: top; vertical-align: top;
margin-right: 18px; margin-right: 18px;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -5px -10px; background-position: -5px -10px;
} }
.icon-tg-title { .icon-tg-title {
@ -899,7 +899,7 @@ a.tg_radio_on:hover i.icon-radio {
margin-left: 12px; margin-left: 12px;
margin-top: -1px; margin-top: -1px;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -18px -50px; background-position: -18px -50px;
} }
@ -1617,7 +1617,7 @@ div.im_message_video_thumb {
height: 18px; height: 18px;
margin: 12px 15px; margin: 12px 15px;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -14px -509px; background-position: -14px -509px;
.im_message_file_button_dl_doc & { .im_message_file_button_dl_doc & {
@ -1638,7 +1638,7 @@ div.im_message_video_thumb {
height: 16px; height: 16px;
margin: 13px 16px; margin: 13px 16px;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -13px -611px; background-position: -13px -611px;
} }
@ -1659,8 +1659,9 @@ div.im_message_video_thumb {
background-position: -2px -542px; background-position: -2px -542px;
} }
.im_history_selectable { .im_history_select_active {
a { a,
.clickable {
pointer-events: none; pointer-events: none;
} }
} }
@ -1912,6 +1913,22 @@ img.im_message_document_thumb {
.im_service_message_wrap { .im_service_message_wrap {
text-align: center; text-align: center;
} }
.im_bot_intro_message_wrap {
max-width: 300px;
padding: 4px 10px;
margin: 10px auto;
color: #000;
line-height: 1.4;
text-align: left;
font-size: 13px;
}
.im_bot_intro_message_header {
font-weight: bold;
text-align: center;
}
.im_bot_intro_message {
margin-top: 10px;
}
.im_service_message { .im_service_message {
display: inline-block; display: inline-block;
min-width: 10px; min-width: 10px;
@ -2040,10 +2057,69 @@ a.im_message_fwd_photo {
} }
.im_message_mymention { .im_message_mymention {
background: #fff8cc; background: #fff8cc;
/*border-bottom: 1px solid #ffe222;*/
/*font-weight: bold;*/
} }
.reply_markup_wrap {
margin: 15px -2px 0;
}
.reply_markup_row {
padding: 4px 0;
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
}
.reply_markup_scrollable_wrap.active-scrollbar & {
margin-right: 6px;
}
}
.reply_markup_button_wrap {
display: inline-block;
padding: 0 4px;
}
.reply_markup_button {
color: #3a6d99;
display: block;
width: 100%;
background: #f0f4f7;
height: 30px;
font-size: 13px;
margin: 0;
padding: 6px 6px;
.reply_markup_h1 & {
height: 170px;
}
.reply_markup_h2 & {
height: 81px;
}
.reply_markup_h3 & {
height: 51px;
}
.reply_markup_h4 & {
height: 36px;
}
}
.reply_markup_button:hover {
color: #3a6d99;
background: #dfe8f0;
}
.reply_markup_button_w1 {width: 100%;}
.reply_markup_button_w2 {width: 50%;}
.reply_markup_button_w3 {width: 33.3333333%;}
.reply_markup_button_w4 {width: 25%;}
.reply_markup_button_w5 {width: 20%;}
.reply_markup_button_w6 {width: 16.6666666%;}
.reply_markup_button_w7 {width: 14.2857142%;}
.reply_markup_button_w8 {width: 12.5%;}
.reply_markup_button_w9 {width: 11.1111111%;}
.reply_markup_button_w10 {width: 10%;}
.reply_markup_button_w11 {width: 9.09090909%;}
.reply_markup_button_w12 {width: 8.33333333%;}
.im_history_not_selected, .im_history_not_selected,
.im_history_empty { .im_history_empty {
visibility: hidden; visibility: hidden;
@ -2206,7 +2282,7 @@ img.img_fullsize {
vertical-align: top; vertical-align: top;
opacity: 0.8; opacity: 0.8;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -9px -335px; background-position: -9px -335px;
} }
@ -2310,14 +2386,11 @@ img.img_fullsize {
.composer_emoji_tooltip_tab_stickers {background-position: -9px -361px; } .composer_emoji_tooltip_tab_stickers {background-position: -9px -361px; }
.composer_emoji_tooltip_tab_stickers.active {background-position: -9px -333px; } .composer_emoji_tooltip_tab_stickers.active {background-position: -9px -333px; }
.nano.composer_emoji_tooltip_content_wrap { .composer_emoji_tooltip_scrollable_container {
height: 174px; height: 174px;
position: relative; position: relative;
} }
.composer_emoji_tooltip_content { .composer_emoji_tooltip_content {
/*position: relative;*/
/*overflow: hidden;
overflow-y: auto;*/
padding-right: 8px; padding-right: 8px;
outline: 0!important; outline: 0!important;
} }
@ -2397,15 +2470,33 @@ a.composer_emoji_btn {
&.emoji-spritesheet-4 { background-size: 884px 182px; } &.emoji-spritesheet-4 { background-size: 884px 182px; }
} }
.composer_dropdown {
.composer_dropdown_wrap {
background: #FFF;
display: none; display: none;
padding: 6px 0; position: absolute;
border: 0; border: 0;
.box-shadow(0px 1px 1px 0px rgba(60,75,87,0.27)); .box-shadow(0px 1px 1px 0px rgba(60,75,87,0.27));
border-radius: 0; border-radius: 0;
margin-top: -5px; margin-top: -5px;
margin-left: -1px;
}
.composer_dropdown_scroller {
}
.composer_dropdown {
position: static;
display: block;
float: none;
top: auto;
left: auto;
border: 0;
border-radius: 0;
padding: 0;
margin: 0;
z-index: auto;
& > li > a { & > li > a {
display: block; display: block;
@ -2457,6 +2548,7 @@ a.composer_emoji_btn {
img& { img& {
width: 32px; width: 32px;
height: 32px; height: 32px;
vertical-align: top;
} }
span& { span& {
@ -2474,6 +2566,36 @@ a.composer_emoji_btn {
vertical-align: top; vertical-align: top;
} }
.composer_dropdown a.composer_command_option {
color: #808080;
line-height: 32px;
padding-right: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.composer_dropdown .composer_command_value {
color: #52719a;
display: inline;
}
.composer_dropdown .composer_command_desc {
display: inline;
color: #808080;
padding-left: 7px;
font-weight: normal;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
a.composer_command_option:hover .composer_command_desc,
a.composer_command_option.composer_autocomplete_option_active .composer_command_desc {
color: #698192;
}
.composer_command_desc .emoji {
vertical-align: text-bottom;
}
.composer_stickerset_title { .composer_stickerset_title {
display: block; display: block;
// clear: both; // clear: both;
@ -2558,6 +2680,67 @@ a.composer_emoji_btn {
} }
} }
.composer_command_btn {
display: block;
position: absolute;
right: 37px;
top: 4px;
cursor: pointer;
padding: 0;
width: 20px;
height: 20px;
}
.icon-slash {
display: inline-block;
width: 20px;
height: 20px;
vertical-align: top;
opacity: 0.8;
.image-2x('../img/icons/General.png', 40px, 848px);
background-position: -10px -790px;
.composer_command_btn.active & {
background-position: -10px -820px;
}
.composer_command_btn:hover & {
opacity: 1.0;
}
}
.composer_keyboard_btn {
display: block;
position: absolute;
right: 37px;
top: 4px;
cursor: pointer;
padding: 0;
width: 20px;
height: 20px;
}
.icon-keyboard {
display: inline-block;
width: 20px;
height: 20px;
vertical-align: top;
opacity: 0.8;
.image-2x('../img/icons/General.png', 40px, 848px);
background-position: -10px -730px;
.composer_keyboard_btn.active & {
background-position: -10px -760px;
}
.composer_keyboard_btn:hover & {
opacity: 1.0;
}
}
.error_modal_window { .error_modal_window {
.modal-dialog { .modal-dialog {
max-width: 350px; max-width: 350px;
@ -2984,13 +3167,6 @@ a.contacts_modal_contact:hover .md_modal_list_peer_description,
} }
} }
.im_edit_panel_title {
text-align: center;
margin: 0;
font-size: 14px;
line-height: 34px;
}
.im_message_focus { .im_message_focus {
.im_message_date, .im_message_date,
.im_message_document_size, .im_message_document_size,
@ -3020,6 +3196,8 @@ a.peer_photo_init {
color: #fff; color: #fff;
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;
.user-select(none);
} }
h5 { h5 {

301
app/less/desktop.less

@ -205,7 +205,7 @@
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin-top: 3px; margin-top: 3px;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -10px -111px; background-position: -10px -111px;
} }
@ -217,7 +217,7 @@
margin-top: 2px; margin-top: 2px;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -11px -135px; background-position: -11px -135px;
} }
@ -228,7 +228,7 @@
margin-top: 1px; margin-top: 1px;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -10px -163px; background-position: -10px -163px;
} }
@ -239,7 +239,7 @@
margin-top: 1px; margin-top: 1px;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -10px -637px; background-position: -10px -637px;
} }
@ -250,7 +250,7 @@
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -10px -193px; background-position: -10px -193px;
} }
} }
@ -378,7 +378,7 @@
margin-right: 12px; margin-right: 12px;
vertical-align: top; vertical-align: top;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: 0 0; background-position: 0 0;
} }
@ -482,7 +482,8 @@
} }
} }
.composer_emoji_tooltip { .composer_emoji_tooltip,
.composer_dropdown_wrap {
z-index: 1001; z-index: 1001;
.nano > .nano-pane { .nano > .nano-pane {
@ -491,11 +492,18 @@
& > .nano-slider { & > .nano-slider {
background: #d1d1d1; background: #d1d1d1;
background : rgba(0,0,0,0.17);
margin: 0 3px 0 4px; margin: 0 3px 0 4px;
} }
} }
} }
.composer_dropdown_wrap .nano > .nano-pane {
top: 3px;
bottom: 3px;
right: -1px;
}
.countries_modal_col { .countries_modal_col {
.nano { .nano {
& > .nano-pane { & > .nano-pane {
@ -694,6 +702,24 @@ a.footer_link.active:active {
} }
} }
.reply_markup_scrollable_container {
.nano > .nano-pane {
background: rgba(137, 160, 179, 0.1);
right: 2px;
width: 3px;
top: 0;
bottom: 0;
.rounded(1px);
& > .nano-slider {
.rounded(1px);
background: #d1d1d1;
background: rgba(137, 160, 179, 0.5);
margin: 0;
}
}
}
.im_history { .im_history {
&_no_dialogs_wrap { &_no_dialogs_wrap {
margin: 122px 170px 60px; margin: 122px 170px 60px;
@ -772,50 +798,13 @@ a.footer_link.active:active {
opacity: 1; opacity: 1;
} }
.icon-message-status {
pointer-events: none;
background: #4eabf1;
border: 0;
display: block;
width: 10px;
height: 10px;
border-radius: 7px;
position: absolute;
margin-left: -26px;
margin-top: 16px;
opacity: 0;
.im_message_unread & {
opacity: 1.0;
}
.im_message_pending & {
opacity: 0.5;
}
}
.im_message_error_btn {
display: none;
.im_message_error & {
display: inline;
}
.icon-message-status {
background: #da564d;
opacity: 0.85;
&:hover {
opacity: 1;
}
}
}
/* Messages edit panel */ /* Messages edit panel */
.im { .im {
&_edit_delete_btn, &_edit_delete_btn,
&_edit_forward_btn, &_edit_forward_btn,
&_edit_reply_btn { &_edit_reply_btn,
&_start_btn {
border-radius: 2px; border-radius: 2px;
padding: 7px 17px; padding: 7px 17px;
font-weight: bold; font-weight: bold;
@ -825,7 +814,7 @@ a.footer_link.active:active {
} }
&_edit_panel_wrap { &_edit_panel_wrap {
padding: 0px 0 43px; padding: 0px 0 41px;
margin: 0 24px 0 12px; margin: 0 24px 0 12px;
} }
@ -849,6 +838,14 @@ a.footer_link.active:active {
text-transform: uppercase; text-transform: uppercase;
} }
&_edit_start_actions {
text-align: center;
text-transform: uppercase;
}
&_start_btn {
padding: 7px 25px;
}
&_selected_count { &_selected_count {
color: #b9cfe3; color: #b9cfe3;
} }
@ -894,55 +891,114 @@ a.footer_link.active:active {
.im_message_selected .im_message_audio_duration, .im_message_selected .im_message_audio_duration,
.im_message_selected .im_message_audio_size, .im_message_selected .im_message_audio_size,
.im_message_selected .im_message_fwd_date, .im_message_selected .im_message_fwd_date,
.im_history_selectable .im_message_outer_wrap:hover .im_message_date, .im_history_select_active .im_message_outer_wrap:hover .im_message_date,
.im_history_selectable .im_message_outer_wrap:hover .im_message_document_size, .im_history_select_active .im_message_outer_wrap:hover .im_message_document_size,
.im_history_selectable .im_message_outer_wrap:hover .im_message_audio_duration, .im_history_select_active .im_message_outer_wrap:hover .im_message_audio_duration,
.im_history_selectable .im_message_outer_wrap:hover .im_message_audio_size, .im_history_select_active .im_message_outer_wrap:hover .im_message_audio_size,
.im_history_selectable .im_message_outer_wrap:hover .im_message_fwd_date { .im_history_select_active .im_message_outer_wrap:hover .im_message_fwd_date {
color: #899daf; color: #899daf;
} }
.im_content_message_select_area {
display: none;
cursor: pointer;
position: absolute;
width: 99px;
height: 58px;
margin: -8px 0 0 -99px;
.user-select(none);
}
.icon-select-tick { .icon-select-tick {
display: none; display: none;
width: 26px;
height: 26px;
margin: 16px 0 0 40px;
.image-2x('../img/icons/IconsetW.png', 42px, 1171px);
background-position: -9px -516px;
} }
@media (min-width: 1024px) { @media (min-width: 1024px) {
.im_content_message_select_area {
display: block;
}
.im_message_wrap { .im_message_wrap {
position: relative; position: relative;
} }
.icon-select-tick {
.im_message_selected &,
.im_history_selectable .im_message_outer_wrap:hover & {
position: absolute;
width: 26px;
height: 26px;
margin: 9px 0 0 -59px;
display: block;
.image-2x('../img/icons/IconsetW.png', 42px, 1171px);
background-position: -9px -481px;
opacity: 0.5;
}
.im_message_selected & {
opacity: 1 !important;
}
.im_grouped_short &,
.im_grouped_short .im_message_outer_wrap:hover & {
margin-top: -2px;
}
.im_message_fwd &,
.im_message_outer_wrap:hover .im_message_fwd & {
margin-top: 10px;
}
.im_grouped_fwd &,
.im_grouped_fwd .im_message_outer_wrap:hover & {
margin-top: 7px;
}
.im_grouped &,
.im_grouped .im_message_outer_wrap:hover & {
margin-top: 7px;
}
.im_grouped_fwd_short &,
.im_grouped_fwd_short .im_message_outer_wrap:hover & {
margin-top: -5px;
}
}
} }
.icon-select-tick { .icon-message-status {
.im_message_selected &, pointer-events: none;
.im_history_selectable .im_message_outer_wrap:hover &, background: #4eabf1;
.im_content_message_select_area:hover & { border: 0;
display: inline-block; display: block;
background-position: -9px -481px; width: 10px;
height: 10px;
border-radius: 7px;
position: absolute;
margin-left: -26px;
margin-top: 16px;
opacity: 0;
.im_message_unread & {
opacity: 1.0;
}
.im_message_pending & {
opacity: 0.5; opacity: 0.5;
} }
.im_message_selected & { .im_grouped_short & {
opacity: 1 !important; margin-top: 5px;
}
.im_message_fwd & {
margin-top: 16px;
}
.im_grouped_fwd & {
margin-top: 13px;
}
.im_grouped & {
margin-top: 13px;
}
.im_grouped_fwd_short & {
margin-top: 2px;
}
}
.im_message_error_btn {
display: none;
.im_message_error & {
display: inline;
}
.icon-message-status {
background: #da564d;
opacity: 0.85;
pointer-events: auto;
&:hover {
opacity: 1;
}
} }
} }
@ -1061,7 +1117,7 @@ a.footer_link.active:active {
.im_send_panel_wrap { .im_send_panel_wrap {
max-width: 554px; max-width: 554px;
padding-bottom: 23px; padding-bottom: 21px;
} }
.im_send_form { .im_send_form {
max-width: 382px; max-width: 382px;
@ -1123,11 +1179,15 @@ a.im_panel_peer_photo .peer_initials {
} }
.im_send_field_wrap { .im_send_field_wrap {
margin-bottom: 15px; margin-bottom: 13px;
position: relative; position: relative;
padding-bottom: 2px;
overflow-x: hidden;
} }
.composer_rich_textarea, .composer_rich_textarea,
.composer_textarea { .composer_textarea {
overflow: none;
overflow-y: scroll;
border-radius: 0; border-radius: 0;
border: 0; border: 0;
box-shadow: none; box-shadow: none;
@ -1145,6 +1205,10 @@ a.im_panel_peer_photo .peer_initials {
outline: none; outline: none;
box-shadow: 0 2px 0 0 #77b7e4; box-shadow: 0 2px 0 0 #77b7e4;
} }
.im_send_field_wrap_2ndbtn & {
padding-right: 65px;
}
} }
.icon-paperclip { .icon-paperclip {
@ -1155,7 +1219,7 @@ a.im_panel_peer_photo .peer_initials {
opacity: 0.8; opacity: 0.8;
margin: 0; margin: 0;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -11px -455px; background-position: -11px -455px;
} }
@ -1204,7 +1268,7 @@ a.im_panel_peer_photo .peer_initials {
vertical-align: top; vertical-align: top;
opacity: 0.8; opacity: 0.8;
.image-2x('../img/icons/General.png', 40px, 778px); .image-2x('../img/icons/General.png', 40px, 848px);
background-position: -10px -399px; background-position: -10px -399px;
} }
@ -1227,7 +1291,7 @@ a.im_panel_peer_photo .peer_initials {
&_form_wrap { &_form_wrap {
a.im_panel_own_photo, a.im_panel_own_photo,
a.im_panel_peer_photo { a.im_panel_peer_photo {
margin-top: 47px; margin-top: 41px;
} }
} }
@ -1730,19 +1794,30 @@ a.im_panel_peer_photo .peer_initials {
max-width: 362px; max-width: 362px;
display: inline-block; display: inline-block;
} }
.im_message_selected .im_message_outer_wrap, .im_message_selected .im_message_outer_wrap {
.im_message_focus .im_message_outer_wrap {
background: #f2f6fa; background: #f2f6fa;
} }
.im_history_selectable { .im_message_focus .im_message_outer_wrap {
.im_message_outer_wrap { background-color: rgba(242, 246, 250, 1.0);
cursor: pointer; animation-name: im_message_focus_fade;
animation-duration: 5s;
}
&:hover { @keyframes im_message_focus_fade {
background: #f2f6fa; from {
background-color: rgba(242, 246, 250, 1.0);
}
to {
background-color: rgba(242, 246, 250, 0);
} }
} }
.im_history_selectable .im_message_outer_wrap {
cursor: pointer;
}
.im_history_select_active .im_message_outer_wrap:hover {
background: #f2f6fa;
} }
.im_message_wrap { .im_message_wrap {
@ -1809,42 +1884,8 @@ a.im_panel_peer_photo .peer_initials {
} }
} }
.im_content_message_select_area {
.im_grouped_short &,
.im_grouped & {
height: 50px;
}
.im_message_fwd & {
margin-top: -4px;
}
.im_grouped_fwd .im_message_fwd &,
.im_grouped_fwd_short .im_message_fwd & {
margin-top: -8px;
}
.im_history_appending & {
height: 52px;
}
}
.icon-select-tick {
.im_message_fwd & {
margin-top: 12px;
}
.im_grouped_short & {
margin-top: 4px;
}
.im_grouped_fwd_short & {
margin-top: 2px;
}
}
.im_grouped_short .icon-message-status,
.im_grouped_fwd_short .icon-message-status {
margin-top: 5px;
}
.im_grouped_fwd .im_message_fwd_from, .im_grouped_fwd .im_message_fwd_from,
.im_grouped_fwd_short .im_message_fwd_from { .im_grouped_fwd_short .im_message_fwd_from {
display: none; display: none;
@ -1859,12 +1900,6 @@ a.im_panel_peer_photo .peer_initials {
display: none; display: none;
} }
} }
.im_grouped_fwd &,
.im_grouped_fwd_short & {
margin-top: 8px;
}
.im_grouped_fwd &,
.im_grouped_fwd_short & { .im_grouped_fwd_short & {
margin-top: 8px; margin-top: 8px;
} }

18
app/less/mobile.less

@ -519,7 +519,7 @@ html {
border: 0; border: 0;
overflow: hidden; overflow: hidden;
} }
.im_history_selectable .im_message_outer_wrap:hover, .im_history_select_active .im_message_outer_wrap:hover,
.im_message_selected { .im_message_selected {
background: #e1e9f0; background: #e1e9f0;
} }
@ -831,10 +831,10 @@ img.im_message_video_thumb,
.im_message_focus .audio_player_duration, .im_message_focus .audio_player_duration,
.im_message_focus .audio_player_size, .im_message_focus .audio_player_size,
.im_message_focus .im_message_fwd_date, .im_message_focus .im_message_fwd_date,
.im_history_selectable .im_message_outer_wrap:hover .im_message_document_size, .im_history_select_active .im_message_outer_wrap:hover .im_message_document_size,
.im_history_selectable .im_message_outer_wrap:hover .audio_player_duration, .im_history_select_active .im_message_outer_wrap:hover .audio_player_duration,
.im_history_selectable .im_message_outer_wrap:hover .audio_player_size, .im_history_select_active .im_message_outer_wrap:hover .audio_player_size,
.im_history_selectable .im_message_outer_wrap:hover .im_message_fwd_date { .im_history_select_active .im_message_outer_wrap:hover .im_message_fwd_date {
color: #68839c; color: #68839c;
} }
@ -1077,10 +1077,10 @@ a.im_dialog {
.im_message_selected .audio_player_title, .im_message_selected .audio_player_title,
.im_message_selected .audio_player_duration, .im_message_selected .audio_player_duration,
.im_message_selected .audio_player_size, .im_message_selected .audio_player_size,
.im_history_selectable .im_message_selected:hover .im_message_document_size, .im_history_select_active .im_message_selected:hover .im_message_document_size,
.im_history_selectable .im_message_selected:hover .audio_player_duration, .im_history_select_active .im_message_selected:hover .audio_player_duration,
.im_history_selectable .im_message_selected:hover .audio_player_size, .im_history_select_active .im_message_selected:hover .audio_player_size,
.im_history_selectable .im_message_selected:hover .im_message_fwd_date { .im_history_select_active .im_message_selected:hover .im_message_fwd_date {
color: #fff; color: #fff;
} }
a.im_message_fwd_author { a.im_message_fwd_author {

8
app/partials/desktop/audio_player.html

@ -7,8 +7,12 @@
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span> <span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span> <span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span>
</div> </div>
<a ng-click="download()" class="audio_player_title" ng-switch="::audio.file_name.length > 0"> <a ng-click="download()" class="audio_player_title" ng-switch="::audio.audioTitle.length > 0 ? 2 : (audio.file_name.length > 0 ? 1 : 0)">
<span ng-switch-when="true" ng-bind="::audio.file_name"></span> <span ng-switch-when="2">
<strong ng-bind="::audio.audioPerformer"></strong>
<span ng-bind="::(audio.audioPerformer ? '– ' : '') + audio.audioTitle"></span>
</span>
<span ng-switch-when="1" 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> <i ng-if="::message.media_unread || false" ng-show="message.media_unread" class="icon icon-audio-unread"></i>

2
app/partials/desktop/chat_modal.html

@ -100,7 +100,7 @@
<div class="md_modal_list_peer_name"> <div class="md_modal_list_peer_name">
<a class="md_modal_list_peer_name" my-user-link="participant.user_id"></a> <a class="md_modal_list_peer_name" my-user-link="participant.user_id"></a>
</div> </div>
<div class="md_modal_list_peer_description" my-user-status="::participant.user_id"></div> <div class="md_modal_list_peer_description" my-user-status="::participant.user_id" bot-chat-privacy="true"></div>
</div> </div>
</div> </div>

4
app/partials/desktop/document_modal.html

@ -16,12 +16,12 @@
</a> </a>
</div> </div>
<div class="media_modal_info_wrap pull-left" ng-switch="messageID > 0"> <div class="media_modal_info_wrap pull-left" ng-if="document.user_id > 0" ng-switch="messageID > 0">
<a class="media_modal_author_photo pull-left" my-peer-photolink="document.user_id" img-class="media_modal_author_photo" watch="true"></a> <a class="media_modal_author_photo pull-left" my-peer-photolink="document.user_id" img-class="media_modal_author_photo" watch="true"></a>
<div class="media_modal_author_name"> <div class="media_modal_author_name">
<a class="media_modal_author" my-user-link="document.user_id" user-watch="true"></a> <a class="media_modal_author" my-user-link="document.user_id" user-watch="true"></a>
</div> </div>
<div class="media_modal_date"> <div class="media_modal_date" ng-if="document.date > 0">
<a ng-switch-when="true" class="media_modal_date" ng-click="goToMessage()" ng-bind="document.date | dateOrTime :true"></a> <a ng-switch-when="true" class="media_modal_date" ng-click="goToMessage()" ng-bind="document.date | dateOrTime :true"></a>
<span ng-switch-default ng-bind="document.date | dateOrTime :true"></span> <span ng-switch-default ng-bind="document.date | dateOrTime :true"></span>
</div> </div>

29
app/partials/desktop/im.html

@ -103,7 +103,7 @@
<div class="im_history_scrollable_wrap nano-content"> <div class="im_history_scrollable_wrap nano-content">
<div class="im_history_scrollable"> <div class="im_history_scrollable">
<div class="im_history" ng-class="{im_history_selectable: historyState.selectActions}"> <div class="im_history" ng-class="{im_history_selectable: !historyState.startBot, im_history_select_active: historyState.selectActions}">
<div ng-if="state.empty" class="im_history_empty" ng-switch="state.mayBeHasMore" my-vertical-position="0.25" padding="true"> <div ng-if="state.empty" class="im_history_empty" ng-switch="state.mayBeHasMore" my-vertical-position="0.25" padding="true">
<span ng-switch-when="true"> <span ng-switch-when="true">
<my-i18n msgid="im_loading_history"></my-i18n><span my-loading-dots></span> <my-i18n msgid="im_loading_history"></my-i18n><span my-loading-dots></span>
@ -140,10 +140,13 @@
<div class="im_bottom_panel_wrap"> <div class="im_bottom_panel_wrap">
<div class="im_edit_panel_wrap clearfix" ng-show="historyState.selectActions"> <div class="im_edit_panel_wrap clearfix" ng-show="historyState.selectActions || historyState.startBot" ng-switch="historyState.startBot != false">
<div class="im_edit_panel_border"></div> <div class="im_edit_panel_border"></div>
<a class="btn btn-md btn-md-primary im_edit_cancel_link" ng-click="selectedCancel()" my-i18n="modal_cancel"></a> <a ng-show="historyState.startBot != 2" class="btn btn-md btn-md-primary im_edit_cancel_link" ng-click="selectedCancel()" my-i18n="modal_cancel"></a>
<div class="im_edit_selected_actions" my-i18n> <div class="im_edit_start_actions" ng-switch-when="true">
<a class="btn btn-primary im_start_btn" ng-click="botStart()" my-i18n="im_start"></a>
</div>
<div class="im_edit_selected_actions" ng-switch-default my-i18n>
<a class="btn btn-primary im_edit_forward_btn" ng-click="selectedForward()" ng-class="{disabled: !selectedCount}" ng-disabled="!selectedCount" my-i18n-format="im_forward"></a> <a class="btn btn-primary im_edit_forward_btn" ng-click="selectedForward()" ng-class="{disabled: !selectedCount}" ng-disabled="!selectedCount" my-i18n-format="im_forward"></a>
<a class="btn btn-primary im_edit_delete_btn" ng-click="selectedDelete()" ng-class="{disabled: !selectedCount}" ng-disabled="!selectedCount" my-i18n-format="im_delete"></a> <a class="btn btn-primary im_edit_delete_btn" ng-click="selectedDelete()" ng-class="{disabled: !selectedCount}" ng-disabled="!selectedCount" my-i18n-format="im_delete"></a>
<a class="btn btn-primary im_edit_reply_btn" ng-click="selectedReply()" ng-show="selectedCount == 1"my-i18n="im_reply"></a> <a class="btn btn-primary im_edit_reply_btn" ng-click="selectedReply()" ng-show="selectedCount == 1"my-i18n="im_reply"></a>
@ -151,7 +154,7 @@
</div> </div>
</div> </div>
<div class="im_send_panel_wrap" ng-hide="historyState.selectActions"> <div class="im_send_panel_wrap" ng-show="!historyState.selectActions &amp;&amp; !historyState.startBot">
<div class="im_send_form_wrap1"> <div class="im_send_form_wrap1">
@ -162,21 +165,23 @@
</a> </a>
<a class="pull-left im_panel_own_photo" my-peer-photolink="ownID" img-class="im_panel_own_photo" watch="true" ng-click="openSettings()" no-open="true"></a> <a class="pull-left im_panel_own_photo" my-peer-photolink="ownID" img-class="im_panel_own_photo" watch="true" ng-click="openSettings()" no-open="true"></a>
<form my-send-form draft-message="draftMessage" mentions="mentions" class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length}"> <form my-send-form draft-message="draftMessage" mentions="mentions" commands="commands" class="im_send_form" ng-class="{im_send_form_empty: !draftMessage.text.length}">
<div class="im_send_reply_wrap" ng-if="draftMessage.replyToMessage != null"> <div class="im_send_reply_wrap" ng-if="draftMessage.replyToMessage != null">
<a class="im_send_reply_cancel" ng-click="draftMessage.replyClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a> <a class="im_send_reply_cancel" ng-mousedown="draftMessage.replyClear()"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<div my-reply-message="draftMessage.replyToMessage"></div> <a class="im_message_reply_wrap" my-reply-message="draftMessage.replyToMessage" watch="true"></a>
</div> </div>
<div class="im_send_field_wrap"> <div class="im_send_field_wrap" ng-class="historyState.replyKeyboard._ == 'replyKeyboardMarkup' ? 'im_send_field_wrap_2ndbtn' : ''">
<a class="composer_emoji_insert_btn"><i class="icon icon-emoji"></i></a> <a class="composer_emoji_insert_btn"><i class="icon icon-emoji"></i></a>
<a class="composer_command_btn" ng-show="!historyState.replyKeyboard && commands.list.length > 0 && !draftMessage.text.length || draftMessage.text[0] == '/'" ng-mousedown="toggleSlash($event)" ng-class="draftMessage.text[0] == '/' ? 'active' : ''"><i class="icon icon-slash"></i></a>
<a class="composer_keyboard_btn" ng-show="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-mousedown="replyKeyboardToggle($event)" ng-class="!historyState.replyKeyboard.pFlags.hidden ? 'active' : ''"><i class="icon icon-keyboard"></i></a>
<div class="im_send_dropbox_wrap" my-i18n="im_photos_drop_text"></div> <div class="im_send_dropbox_wrap" my-i18n="im_photos_drop_text"></div>
<textarea ng-model="draftMessage.text" placeholder="{{'im_message_field_placeholder' | i18n}}" class="form-control im_message_field no_outline" dir="auto"></textarea> <textarea ng-model="draftMessage.text" placeholder="{{'im_message_field_placeholder' | i18n}}" class="form-control im_message_field no_outline" dir="auto"></textarea>
</div> </div>
<div class="clearfix"> <div class="im_send_buttons_wrap clearfix">
<button type="submit" class="btn btn-md im_submit" my-i18n="im_submit_message"></button> <button type="submit" class="btn btn-md im_submit" my-i18n="im_submit_message"></button>
<div class="im_attach pull-left"> <div class="im_attach pull-left">
@ -190,8 +195,12 @@
</div> </div>
<div class="composer_emoji_panel"></div> <div class="composer_emoji_panel"></div>
</div>
<div class="im_send_keyboard_wrap" ng-if="historyState.replyKeyboard._ == 'replyKeyboardMarkup'" ng-show="!historyState.replyKeyboard.pFlags.hidden">
<div my-reply-markup="historyState.replyKeyboard"></div>
</div> </div>
</form> </form>
</div> </div>

12
app/partials/desktop/message.html

@ -3,8 +3,12 @@
<div class="im_message_wrap clearfix" ng-switch="::historyMessage._ == 'messageService'"> <div class="im_message_wrap clearfix" ng-switch="::historyMessage._ == 'messageService'">
<div class="im_service_message_wrap" ng-switch-when="true"> <div class="im_service_message_wrap" ng-switch-when="true" ng-switch="historyMessage.action._ == 'messageActionBotIntro'">
<div class="im_service_message"> <div ng-switch-when="true" class="im_bot_intro_message_wrap">
<div class="im_bot_intro_message_header" my-i18n="message_service_bot_intro_header"></div>
<div class="im_bot_intro_message" ng-bind-html="::historyMessage.action.rDescription"></div>
</div>
<div ng-switch-default class="im_service_message">
<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>
<span class="im_message_service" my-service-message></span> <span class="im_message_service" my-service-message></span>
</div> </div>
@ -20,9 +24,7 @@
</div> </div>
<div ng-switch-default class="im_content_message_wrap" ng-class="::[historyMessage.out ? 'im_message_out' : 'im_message_in', historyMessage.fwd_from_id > 0 ? 'im_message_fwd' : '']"> <div ng-switch-default class="im_content_message_wrap" ng-class="::[historyMessage.out ? 'im_message_out' : 'im_message_in', historyMessage.fwd_from_id > 0 ? 'im_message_fwd' : '']">
<div class="im_content_message_select_area"> <i class="icon icon-select-tick"></i>
<i class="icon icon-select-tick"></i>
</div>
<a class="im_message_error_btn" ng-if="::historyMessage.pending || historyMessage.error || false" ng-click="historyMessage.send()"> <a class="im_message_error_btn" ng-if="::historyMessage.pending || historyMessage.error || false" ng-click="historyMessage.send()">
<i class="icon-message-status" tooltip="Try again"></i> <i class="icon-message-status" tooltip="Try again"></i>

2
app/partials/desktop/message_attach_document.html

@ -2,7 +2,7 @@
<div ng-switch-when="gif" my-load-gif document="document"></div> <div ng-switch-when="gif" my-load-gif document="document"></div>
<div ng-switch-when="sticker" my-load-sticker document="document"></div> <div ng-switch-when="sticker" my-load-sticker document="document" open="true"></div>
<div ng-switch-when="audio" class="im_message_audio"> <div ng-switch-when="audio" class="im_message_audio">
<div my-audio-player audio="document"></div> <div my-audio-player audio="document"></div>

4
app/partials/desktop/photo_modal.html

@ -22,12 +22,12 @@
</a> </a>
</div> </div>
<div class="media_modal_info_wrap pull-left" ng-if="!webpageID"> <div class="media_modal_info_wrap pull-left" ng-if="!webpageID && photo.user_id">
<a class="media_modal_author_photo pull-left" my-peer-photolink="photo.user_id" img-class="media_modal_author_photo" watch="true"></a> <a class="media_modal_author_photo pull-left" my-peer-photolink="photo.user_id" img-class="media_modal_author_photo" watch="true"></a>
<div class="media_modal_author_name"> <div class="media_modal_author_name">
<a class="media_modal_author" my-user-link="photo.user_id" user-watch="true"></a> <a class="media_modal_author" my-user-link="photo.user_id" user-watch="true"></a>
</div> </div>
<div class="media_modal_date" ng-switch="messageID > 0"> <div class="media_modal_date" ng-if="photo.date > 0" ng-switch="messageID > 0">
<a ng-switch-when="true" class="media_modal_date" ng-click="goToMessage()" ng-bind="photo.date | dateOrTime :true"></a> <a ng-switch-when="true" class="media_modal_date" ng-click="goToMessage()" ng-bind="photo.date | dateOrTime :true"></a>
<span ng-switch-default ng-bind="photo.date | dateOrTime :true"></span> <span ng-switch-default ng-bind="photo.date | dateOrTime :true"></span>
</div> </div>

9
app/partials/desktop/reply_markup.html

@ -0,0 +1,9 @@
<div class="reply_markup_wrap">
<div class="reply_markup" ng-class="replyMarkup.splitCount ? 'reply_markup_h' + replyMarkup.splitCount : ''">
<div class="reply_markup_row" ng-repeat="row in replyMarkup.rows">
<div class="reply_markup_button_wrap" ng-class="'reply_markup_button_w' + row.buttons.length" ng-repeat="button in row.buttons">
<button class="btn reply_markup_button" ng-bind-html="::button.rText" ng-click="buttonSend(button)"></button>
</div>
</div>
</div>
</div>

1
app/partials/desktop/reply_message.html

@ -8,6 +8,7 @@
class="im_message_reply_thumb" class="im_message_reply_thumb"
my-load-thumb my-load-thumb
thumb="thumb" thumb="thumb"
watch="true"
/> />
</div> </div>
<div class="im_message_reply_author" ng-switch-default> <div class="im_message_reply_author" ng-switch-default>

27
app/partials/desktop/user_modal.html

@ -4,7 +4,7 @@
<div class="md_modal_title_wrap"> <div class="md_modal_title_wrap">
<div class="md_modal_actions_wrap clearfix"> <div class="md_modal_actions_wrap clearfix">
<a class="md_modal_action md_modal_action_close" ng-click="$close()" my-i18n="modal_close"></a> <a class="md_modal_action md_modal_action_close" ng-click="$close()" my-i18n="modal_close"></a>
<a class="md_modal_action" ng-if="user._ == 'userContact'" ng-click="importContact(true)" my-i18n="modal_edit"></a> <a class="md_modal_action" ng-if="user.pFlags.contact" ng-click="importContact(true)" my-i18n="modal_edit"></a>
</div> </div>
<div class="md_modal_title" my-i18n="user_modal_contact_info"></div> <div class="md_modal_title" my-i18n="user_modal_contact_info"></div>
</div> </div>
@ -36,6 +36,13 @@
<div class="md_modal_iconed_section_wrap md_modal_iconed_section_number" ng-if="user.phone || user.username"> <div class="md_modal_iconed_section_wrap md_modal_iconed_section_number" ng-if="user.phone || user.username">
<i class="md_modal_section_icon md_modal_section_icon_phone"></i> <i class="md_modal_section_icon md_modal_section_icon_phone"></i>
<div class="md_modal_section_param_wrap" ng-if="user.pFlags.bot &amp;&amp; bot_info.rAbout">
<div class="md_modal_section_param_value">
<span ng-bind-html="bot_info.rAbout"></span>
</div>
<div class="md_modal_section_param_name" my-i18n="user_modal_about"></div>
</div>
<div class="md_modal_section_param_wrap" ng-if="user.phone"> <div class="md_modal_section_param_wrap" ng-if="user.phone">
<div class="md_modal_section_param_value" ng-bind="user.phone | phoneNumber"></div> <div class="md_modal_section_param_value" ng-bind="user.phone | phoneNumber"></div>
<div class="md_modal_section_param_name" my-i18n="user_modal_phone"></div> <div class="md_modal_section_param_name" my-i18n="user_modal_phone"></div>
@ -62,7 +69,19 @@
<div class="md_modal_iconed_section_wrap md_modal_iconed_section_link" ng-init="f.showMoreActions = !user.phone.length"> <div class="md_modal_iconed_section_wrap md_modal_iconed_section_link" ng-init="f.showMoreActions = !user.phone.length">
<i class="md_modal_section_icon md_modal_section_icon_more"></i> <i class="md_modal_section_icon md_modal_section_icon_more"></i>
<div class="md_modal_section_link_wrap" ng-if="user.phone.length > 0 &amp;&amp; user._ != 'userContact' &amp;&amp; user._ != 'userSelf'"> <div class="md_modal_section_link_wrap" ng-if="user.pFlags.bot &amp;&amp; !user.pFlags.botNoGroups">
<a class="md_modal_section_link" ng-click="inviteToGroup()" my-i18n="user_modal_add_to_group"></a>
</div>
<div class="md_modal_section_link_wrap" ng-if="bot_info.commands.settings != null">
<a class="md_modal_section_link" ng-click="sendCommand('settings')" my-i18n="user_modal_bot_settings"></a>
</div>
<div class="md_modal_section_link_wrap" ng-if="bot_info.commands.help != null">
<a class="md_modal_section_link" ng-click="sendCommand('help')" my-i18n="user_modal_bot_help"></a>
</div>
<div class="md_modal_section_link_wrap" ng-if="user.phone.length > 0 &amp;&amp; !user.pFlags.contact &amp;&amp; !user.pFlags.self">
<a class="md_modal_section_link" ng-click="importContact()" my-i18n="user_modal_add_contact"></a> <a class="md_modal_section_link" ng-click="importContact()" my-i18n="user_modal_add_contact"></a>
</div> </div>
@ -70,12 +89,12 @@
<a class="md_modal_section_link" ng-click="shareContact()" my-i18n="user_modal_share_contact"></a> <a class="md_modal_section_link" ng-click="shareContact()" my-i18n="user_modal_share_contact"></a>
</div> </div>
<div class="md_modal_section_link_wrap" ng-if="f.showMoreActions &amp;&amp; user._ == 'userContact'"> <div class="md_modal_section_link_wrap" ng-if="f.showMoreActions &amp;&amp; user.pFlags.contact">
<a class="md_modal_section_link" ng-click="deleteContact()" my-i18n="user_modal_delete_contact"></a> <a class="md_modal_section_link" ng-click="deleteContact()" my-i18n="user_modal_delete_contact"></a>
</div> </div>
<div class="md_modal_section_link_wrap" ng-if="f.showMoreActions &amp;&amp; user._ != 'userSelf'"> <div class="md_modal_section_link_wrap" ng-if="f.showMoreActions &amp;&amp; !user.pFlags.self">
<a class="md_modal_section_link" ng-click="toggleBlock(!blocked)" ng-switch="blocked"> <a class="md_modal_section_link" ng-click="toggleBlock(!blocked)" ng-switch="blocked">
<my-i18n ng-switch-when="true" msgid="user_modal_unblock_user"></my-i18n> <my-i18n ng-switch-when="true" msgid="user_modal_unblock_user"></my-i18n>
<my-i18n ng-switch-default msgid="user_modal_block_user"></my-i18n> <my-i18n ng-switch-default msgid="user_modal_block_user"></my-i18n>

4
app/partials/desktop/video_modal.html

@ -16,12 +16,12 @@
</a> </a>
</div> </div>
<div class="media_modal_info_wrap pull-left" ng-switch="messageID > 0"> <div class="media_modal_info_wrap pull-left" ng-if="video.user_id > 0" ng-switch="messageID > 0">
<a class="media_modal_author_photo pull-left" my-peer-photolink="video.user_id" img-class="media_modal_author_photo" watch="true"></a> <a class="media_modal_author_photo pull-left" my-peer-photolink="video.user_id" img-class="media_modal_author_photo" watch="true"></a>
<div class="media_modal_author_name"> <div class="media_modal_author_name">
<a class="media_modal_author" my-user-link="video.user_id" user-watch="true"></a> <a class="media_modal_author" my-user-link="video.user_id" user-watch="true"></a>
</div> </div>
<div class="media_modal_date"> <div class="media_modal_date" ng-if="video.date > 0">
<a ng-switch-when="true" class="media_modal_date" ng-click="goToMessage()" ng-bind="video.date | dateOrTime :true"></a> <a ng-switch-when="true" class="media_modal_date" ng-click="goToMessage()" ng-bind="video.date | dateOrTime :true"></a>
<span ng-switch-default ng-bind="video.date | dateOrTime :true"></span> <span ng-switch-default ng-bind="video.date | dateOrTime :true"></span>
</div> </div>

8
app/partials/mobile/audio_player.html

@ -7,8 +7,12 @@
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span> <span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span> <span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span>
</div> </div>
<a ng-click="download()" class="audio_player_title" ng-switch="::audio.file_name.length > 0"> <a ng-click="download()" class="audio_player_title" ng-switch="::audio.audioTitle.length > 0 ? 2 : (audio.file_name.length > 0 ? 1 : 0)">
<span ng-switch-when="true" ng-bind="::audio.file_name"></span> <span ng-switch-when="2">
<strong ng-bind="::audio.audioPerformer"></strong>
<span ng-bind="::(audio.audioPerformer ? '– ' : '') + audio.audioTitle"></span>
</span>
<span ng-switch-when="1" 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>
<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">

2
app/partials/mobile/message_attach_document.html

@ -2,7 +2,7 @@
<div ng-switch-when="gif" my-load-gif document="document"></div> <div ng-switch-when="gif" my-load-gif document="document"></div>
<div ng-switch-when="sticker" my-load-sticker document="document"></div> <div ng-switch-when="sticker" my-load-sticker document="document" open="true"></div>
<div ng-switch-when="audio" class="im_message_audio"> <div ng-switch-when="audio" class="im_message_audio">
<div my-audio-player audio="document"></div> <div my-audio-player audio="document"></div>

2
app/partials/mobile/photo_modal.html

@ -12,7 +12,7 @@
</a> </a>
</div> </div>
<div class="media_modal_info_wrap"> <div class="media_modal_info_wrap" ng-if="photo.user_id > 0">
<a class="media_modal_author" my-user-link="photo.user_id" user-watch="true"></a> <a class="media_modal_author" my-user-link="photo.user_id" user-watch="true"></a>
<br/> <br/>
<span class="media_modal_date" ng-bind="photo.date | dateOrTime :true"></span> <span class="media_modal_date" ng-bind="photo.date | dateOrTime :true"></span>

6
app/partials/mobile/user_modal.html

@ -11,13 +11,13 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li ng-if="user._ == 'userContact'"> <li ng-if="user.pFlags.contact">
<a ng-click="importContact(true)" my-i18n="user_modal_edit_contact"></a> <a ng-click="importContact(true)" my-i18n="user_modal_edit_contact"></a>
</li> </li>
<li ng-if="user._ == 'userContact'"> <li ng-if="user.pFlags.contact">
<a ng-click="deleteContact()" my-i18n="user_modal_delete_contact"></a> <a ng-click="deleteContact()" my-i18n="user_modal_delete_contact"></a>
</li> </li>
<li ng-if="user.phone.length > 0 &amp;&amp; user._ != 'userContact' &amp;&amp; user._ != 'userSelf'"> <li ng-if="user.phone.length > 0 &amp;&amp; !user.pFlags.contact &amp;&amp; !user.pFlags.self">
<a ng-click="importContact()" my-i18n="user_modal_add_contact"></a> <a ng-click="importContact()" my-i18n="user_modal_add_contact"></a>
</li> </li>
<li> <li>

2
app/partials/mobile/video_modal.html

@ -12,7 +12,7 @@
</a> </a>
</div> </div>
<div class="media_modal_info_wrap"> <div class="media_modal_info_wrap" ng-if="video.user_id > 0">
<a class="media_modal_author" my-user-link="video.user_id" user-watch="true"></a> <a class="media_modal_author" my-user-link="video.user_id" user-watch="true"></a>
<br/> <br/>
<span class="media_modal_date" ng-bind="video.date | dateOrTime :true"></span> <span class="media_modal_date" ng-bind="video.date | dateOrTime :true"></span>

1
app/vendor/jquery.nanoscroller/nanoscroller.js vendored

@ -743,6 +743,7 @@
} else { } else {
this.slider.show(); this.slider.show();
} }
this.$el.toggleClass('active-scrollbar', this.isActive);
this.pane.css({ this.pane.css({
opacity: (this.options.alwaysVisible ? 1 : ''), opacity: (this.options.alwaysVisible ? 1 : ''),
visibility: (this.options.alwaysVisible ? 'visible' : '') visibility: (this.options.alwaysVisible ? 'visible' : '')

10321
app/vendor/libwebpjs/libwebp-0.1.13.js vendored

File diff suppressed because one or more lines are too long

4079
app/vendor/libwebpjs/libwebp-0.2.0.js vendored

File diff suppressed because it is too large Load Diff

2
app/webogram.appcache

@ -1,6 +1,6 @@
CACHE MANIFEST CACHE MANIFEST
# 61 # 62
NETWORK: NETWORK:
* *

Loading…
Cancel
Save