Browse Source

Added support for drafts

master
Igor Zhukov 9 years ago
parent
commit
7909731d1e
  1. 113
      app/js/controllers.js
  2. 96
      app/js/lib/ng_utils.js
  3. 1
      app/js/locales/en-us.json
  4. 493
      app/js/messages_manager.js
  5. 259
      app/js/services.js
  6. 8
      app/less/app.less
  7. 26
      app/partials/desktop/dialog.html
  8. 3
      app/partials/desktop/im.html
  9. 26
      app/partials/mobile/dialog.html

113
app/js/controllers.js

@ -753,6 +753,21 @@ angular.module('myApp.controllers', ['myApp.i18n'])
deleteDialog(dialog.peerID); deleteDialog(dialog.peerID);
}); });
$scope.$on('draft_updated', function (e, draftUpdate) {
var curDialog, i;
for (i = 0; i < $scope.dialogs.length; i++) {
curDialog = $scope.dialogs[i];
if (curDialog.peerID == draftUpdate.peerID) {
curDialog.draft = draftUpdate.draft;
if (i > 0 && draftUpdate.draft) {
$scope.dialogs.splice(i, 1);
$scope.dialogs.unshift(curDialog);
}
break;
}
}
});
$scope.$on('history_delete', function (e, historyUpdate) { $scope.$on('history_delete', function (e, historyUpdate) {
for (var i = 0; i < $scope.dialogs.length; i++) { for (var i = 0; i < $scope.dialogs.length; i++) {
if ($scope.dialogs[i].peerID == historyUpdate.peerID) { if ($scope.dialogs[i].peerID == historyUpdate.peerID) {
@ -2147,13 +2162,13 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.$on('user_update', angular.noop); $scope.$on('user_update', angular.noop);
}) })
.controller('AppImSendController', function ($q, $scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, AppInlineBotsManager, MtpApiFileManager, RichTextProcessor) { .controller('AppImSendController', function ($rootScope, $q, $scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppMessagesManager, AppInlineBotsManager, MtpApiFileManager, DraftsManager, RichTextProcessor) {
$scope.$watch('curDialog.peer', resetDraft); $scope.$watch('curDialog.peer', resetDraft);
$scope.$on('user_update', angular.noop); $scope.$on('user_update', angular.noop);
$scope.$on('peer_draft_attachment', applyDraftAttachment); $scope.$on('peer_draft_attachment', applyDraftAttachment);
$scope.$on('reply_selected', function (e, messageID) { $scope.$on('reply_selected', function (e, messageID) {
replySelect(messageID); replySelect(messageID, true);
}); });
$scope.$on('ui_typing', onTyping); $scope.$on('ui_typing', onTyping);
@ -2188,6 +2203,18 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.replyKeyboardToggle = replyKeyboardToggle; $scope.replyKeyboardToggle = replyKeyboardToggle;
$scope.toggleSlash = toggleSlash; $scope.toggleSlash = toggleSlash;
$rootScope.$watch('idle.isIDLE', function (newVal) {
if (newVal && $scope.curDialog.peerID) {
DraftsManager.syncDraft($scope.curDialog.peerID);
}
});
$scope.$on('draft_updated', function (e, draftUpdate) {
if (draftUpdate.peerID == $scope.curDialog.peerID) {
getDraft();
}
});
var replyToMarkup = false; var replyToMarkup = false;
var forceDraft = false; var forceDraft = false;
@ -2200,9 +2227,9 @@ angular.module('myApp.controllers', ['myApp.i18n'])
if (angular.isString(text) && text.length > 0) { if (angular.isString(text) && text.length > 0) {
text = RichTextProcessor.parseEmojis(text); text = RichTextProcessor.parseEmojis(text);
var timeout = 0;
var options = { var options = {
replyToMsgID: $scope.draftMessage.replyToMessage && $scope.draftMessage.replyToMessage.mid replyToMsgID: $scope.draftMessage.replyToMessage && $scope.draftMessage.replyToMessage.mid,
clearDraft: true
}; };
do { do {
AppMessagesManager.sendText($scope.curDialog.peerID, text.substr(0, 4096), options); AppMessagesManager.sendText($scope.curDialog.peerID, text.substr(0, 4096), options);
@ -2213,6 +2240,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
if (forceDraft == $scope.curDialog.peer) { if (forceDraft == $scope.curDialog.peer) {
forceDraft = false; forceDraft = false;
} else {
DraftsManager.changeDraft($scope.curDialog.peerID);
} }
resetDraft(); resetDraft();
@ -2326,7 +2355,14 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}); });
} }
function resetDraft (newPeer) { function resetDraft (newPeer, prevPeer) {
if (prevPeer) {
var prevPeerID = AppPeersManager.getPeerID(prevPeer);
if (prevPeerID) {
DraftsManager.syncDraft(prevPeerID);
}
}
updateMentions(); updateMentions();
updateCommands(); updateCommands();
replyClear(); replyClear();
@ -2347,13 +2383,20 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
fwdsClear(); fwdsClear();
getDraft();
}
if (newPeer) { function getDraft() {
Storage.get('draft' + $scope.curDialog.peerID).then(function (draftText) { if ($scope.curDialog.peerID) {
// console.log('Restore draft', 'draft' + $scope.curDialog.peerID, draftText); DraftsManager.getDraft($scope.curDialog.peerID).then(function (draftData) {
$scope.draftMessage.text = draftText || ''; $scope.draftMessage.text = draftData ? draftData.text : '';
$scope.draftMessage.isBroadcast = AppPeersManager.isChannel($scope.curDialog.peerID) && !AppPeersManager.isMegagroup($scope.curDialog.peerID); $scope.draftMessage.isBroadcast = AppPeersManager.isChannel($scope.curDialog.peerID) && !AppPeersManager.isMegagroup($scope.curDialog.peerID);
// console.log('send broadcast', $scope.draftMessage); if (draftData.replyToMsgID) {
var replyToMsgID = draftData.replyToMsgID;
replySelect(replyToMsgID);
} else {
replyClear();
}
$scope.$broadcast('ui_peer_draft'); $scope.$broadcast('ui_peer_draft');
}); });
} else { } else {
@ -2364,7 +2407,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
function applyDraftAttachment (e, attachment) { function applyDraftAttachment (e, attachment) {
// console.log('apply draft attach', attachment); console.log('apply draft attach', attachment);
if (!attachment || !attachment._) { if (!attachment || !attachment._) {
return; return;
} }
@ -2386,14 +2429,10 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}, 1000); }, 1000);
} }
else if (attachment._ == 'fwd_messages') { else if (attachment._ == 'fwd_messages') {
forceDraft = $scope.curDialog.peer; $timeout(function () {
$scope.draftMessage.fwdMessages = attachment.id; $scope.draftMessage.fwdMessages = attachment.id;
$scope.$broadcast('ui_peer_reply'); $scope.$broadcast('ui_peer_reply');
var peerID = AppPeersManager.getPeerID($scope.curDialog.peer); }, 100);
Storage.get('draft' + peerID).then(function (draftText) {
$scope.draftMessage.text = draftText || '';
$scope.$broadcast('ui_peer_draft');
});
} }
else if (attachment._ == 'inline_query') { else if (attachment._ == 'inline_query') {
var mention = attachment.mention; var mention = attachment.mention;
@ -2413,13 +2452,20 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
} }
function replySelect(messageID) { function replySelect(messageID, byUser) {
$scope.draftMessage.replyToMessage = AppMessagesManager.wrapForDialog(messageID); $scope.draftMessage.replyToMessage = AppMessagesManager.wrapSingleMessage(messageID);
$scope.$broadcast('ui_peer_reply'); $scope.$broadcast('ui_peer_reply');
replyToMarkup = false; replyToMarkup = false;
if (byUser) {
DraftsManager.changeDraft($scope.curDialog.peerID, {
text: $scope.draftMessage.text,
replyToMsgID: messageID
});
}
} }
function replyClear() { function replyClear(byUser) {
var message = $scope.draftMessage.replyToMessage; var message = $scope.draftMessage.replyToMessage;
if (message && if (message &&
$scope.historyState.replyKeyboard && $scope.historyState.replyKeyboard &&
@ -2430,6 +2476,12 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
delete $scope.draftMessage.replyToMessage; delete $scope.draftMessage.replyToMessage;
$scope.$broadcast('ui_peer_reply'); $scope.$broadcast('ui_peer_reply');
if (byUser) {
DraftsManager.changeDraft($scope.curDialog.peerID, {
text: $scope.draftMessage.text
});
}
} }
function fwdsClear () { function fwdsClear () {
@ -2512,16 +2564,15 @@ angular.module('myApp.controllers', ['myApp.i18n'])
if (!$scope.historyFilter.mediaType && !$scope.historyState.skipped) { if (!$scope.historyFilter.mediaType && !$scope.historyState.skipped) {
AppMessagesManager.readHistory($scope.curDialog.peerID); AppMessagesManager.readHistory($scope.curDialog.peerID);
} }
var backupDraftObj = {};
backupDraftObj['draft' + $scope.curDialog.peerID] = newVal;
Storage.set(backupDraftObj);
// console.log(dT(), 'draft save', backupDraftObj);
} else {
Storage.remove('draft' + $scope.curDialog.peerID);
// console.log(dT(), 'draft delete', 'draft' + $scope.curDialog.peerID);
} }
checkInlinePattern(newVal); if ($scope.curDialog.peerID) {
var replyToMessage = $scope.draftMessage.replyToMessage;
DraftsManager.changeDraft($scope.curDialog.peerID, {
text: newVal,
replyToMsgID: replyToMessage && replyToMessage.mid
});
checkInlinePattern(newVal);
}
} }
var inlineUsernameRegex = /^@([a-zA-Z\d_]{1,32})( | )([\s\S]*)$/; var inlineUsernameRegex = /^@([a-zA-Z\d_]{1,32})( | )([\s\S]*)$/;

96
app/js/lib/ng_utils.js

@ -1258,6 +1258,7 @@ angular.module('izhukov.utils', [])
return { return {
wrapRichText: wrapRichText, wrapRichText: wrapRichText,
wrapPlainText: wrapPlainText, wrapPlainText: wrapPlainText,
wrapDraftText: wrapDraftText,
wrapUrl: wrapUrl, wrapUrl: wrapUrl,
parseEntities: parseEntities, parseEntities: parseEntities,
parseMarkdown: parseMarkdown, parseMarkdown: parseMarkdown,
@ -1731,6 +1732,72 @@ angular.module('izhukov.utils', [])
return $sce.trustAs('html', text); return $sce.trustAs('html', text);
} }
function wrapDraftText (text, options) {
if (!text || !text.length) {
return '';
}
options = options || {};
var entities = options.entities;
if (entities === undefined) {
entities = parseEntities(text, options);
}
var i = 0;
var len = entities.length;
var entity;
var entityText;
var skipEntity;
var code = [];
var lastOffset = 0;
for (i = 0; i < len; i++) {
entity = entities[i];
if (entity.offset > lastOffset) {
code.push(
text.substr(lastOffset, entity.offset - lastOffset)
);
}
else if (entity.offset < lastOffset) {
continue;
}
skipEntity = false;
entityText = text.substr(entity.offset, entity.length);
switch (entity._) {
case 'messageEntityEmoji':
code.push(
':',
entity.title,
':'
);
break;
case 'messageEntityCode':
code.push(
'`', entityText, '`'
);
break;
case 'messageEntityPre':
code.push(
'```', entityText, '```'
);
break;
default:
skipEntity = true;
}
lastOffset = entity.offset + (skipEntity ? 0 : entity.length);
}
code.push(text.substr(lastOffset));
console.log(code, entities);
return code.join('');
}
function checkBrackets(url) { function checkBrackets(url) {
var urlLength = url.length, var urlLength = url.length,
urlOpenBrackets = url.split('(').length - 1, urlOpenBrackets = url.split('(').length - 1,
@ -1818,7 +1885,34 @@ angular.module('izhukov.utils', [])
return url; return url;
} }
}); })
.service('ServerTimeManager', function (Storage) {
var timestampNow = tsNow(true);
var midnightNoOffset = timestampNow - (timestampNow % 86400);
var midnightOffseted = new Date();
midnightOffseted.setHours(0);
midnightOffseted.setMinutes(0);
midnightOffseted.setSeconds(0);
var midnightOffset = midnightNoOffset - (Math.floor(+midnightOffseted / 1000));
var serverTimeOffset = 0;
var timeParams = {
midnightOffset: midnightOffset,
serverTimeOffset: serverTimeOffset
};
Storage.get('server_time_offset').then(function (to) {
if (to) {
serverTimeOffset = to;
timeParams.serverTimeOffset = to;
}
});
return timeParams;
})

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

@ -291,6 +291,7 @@
"conversation_message_deleted": "deleted message", "conversation_message_deleted": "deleted message",
"conversation_you": "You", "conversation_you": "You",
"conversation_draft": "Draft:",
"conversation_media_photo": "Photo", "conversation_media_photo": "Photo",
"conversation_media_video": "Video", "conversation_media_video": "Video",
"conversation_media_document": "File", "conversation_media_document": "File",

493
app/js/messages_manager.js

@ -9,7 +9,7 @@
angular.module('myApp.services') angular.module('myApp.services')
.service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, $sce, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppDocsManager, AppStickersManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, Storage, AppProfileManager, TelegramMeWebService, ErrorService, StatusManager, _) { .service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, $sce, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppDocsManager, AppStickersManager, AppMessagesIDsManager, DraftsManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, ServerTimeManager, RichTextProcessor, NotificationsManager, Storage, AppProfileManager, TelegramMeWebService, ErrorService, StatusManager, _) {
var messagesStorage = {}; var messagesStorage = {};
var messagesForHistory = {}; var messagesForHistory = {};
@ -35,18 +35,6 @@ angular.module('myApp.services')
needIncrementMessageViews = [], needIncrementMessageViews = [],
incrementMessageViewsTimeout = false; incrementMessageViewsTimeout = false;
var serverTimeOffset = 0,
timestampNow = tsNow(true),
midnightNoOffset = timestampNow - (timestampNow % 86400),
midnightOffseted = new Date(),
midnightOffset;
Storage.get('server_time_offset').then(function (to) {
if (to) {
serverTimeOffset = to;
}
});
var maxSeenID = false; var maxSeenID = false;
if (Config.Modes.packed) { if (Config.Modes.packed) {
Storage.get('max_seen_msg').then(function (maxID) { Storage.get('max_seen_msg').then(function (maxID) {
@ -58,11 +46,6 @@ angular.module('myApp.services')
var dateOrTimeFilter = $filter('dateOrTime'); var dateOrTimeFilter = $filter('dateOrTime');
var fwdMessagesPluralize = _.pluralize('conversation_forwarded_X_messages'); var fwdMessagesPluralize = _.pluralize('conversation_forwarded_X_messages');
midnightOffseted.setHours(0);
midnightOffseted.setMinutes(0);
midnightOffseted.setSeconds(0);
midnightOffset = midnightNoOffset - (Math.floor(+midnightOffseted / 1000));
NotificationsManager.start(); NotificationsManager.start();
var allDialogsLoaded = false var allDialogsLoaded = false
@ -141,24 +124,44 @@ angular.module('myApp.services')
return []; return [];
} }
function saveChannelDialog (channelID, dialog) { function saveConversation (dialog) {
var peerID = -channelID; var peerID = AppPeersManager.getPeerID(dialog.peer);
if (!peerID) {
return false;
}
var channelID = AppPeersManager.isChannel(peerID) ? -peerID : 0;
var peerText = AppPeersManager.getPeerSearchText(peerID); var peerText = AppPeersManager.getPeerSearchText(peerID);
SearchIndexManager.indexObject(peerID, peerText, dialogsIndex); SearchIndexManager.indexObject(peerID, peerText, dialogsIndex);
var isMegagroup = AppChatsManager.isMegagroup(channelID); var isMegagroup = AppPeersManager.isMegagroup(channelID);
var mid = getFullMessageID(dialog.top_message, channelID); var mid = AppMessagesIDsManager.getFullMessageID(dialog.top_message, channelID);
var message = getMessage(mid); var message = getMessage(mid);
var offsetDate = message.date; var offsetDate = message.date;
if (!channelID && peerID < 0) {
var chat = AppChatsManager.getChat(-peerID);
if (chat && chat.migrated_to && chat.pFlags.deactivated) {
var migratedToPeer = AppPeersManager.getPeerID(chat.migrated_to);
migratedFromTo[peerID] = migratedToPeer;
migratedToFrom[migratedToPeer] = peerID;
return;
}
}
dialog.top_message = mid; dialog.top_message = mid;
dialog.read_inbox_max_id = getFullMessageID(dialog.read_inbox_max_id, channelID); dialog.read_inbox_max_id = AppMessagesIDsManager.getFullMessageID(dialog.read_inbox_max_id, channelID);
dialog.read_outbox_max_id = getFullMessageID(dialog.read_outbox_max_id, channelID); dialog.read_outbox_max_id = AppMessagesIDsManager.getFullMessageID(dialog.read_outbox_max_id, channelID);
var topDate = message.date; var topDate = message.date;
var channel = AppChatsManager.getChat(channelID); if (channelID) {
if (!topDate || channel.date && channel.date > topDate) { var channel = AppChatsManager.getChat(channelID);
topDate = channel.date; if (!topDate || channel.date && channel.date > topDate) {
topDate = channel.date;
}
}
var savedDraft = DraftsManager.saveDraft(peerID, dialog.draft);
if (savedDraft && savedDraft.date > topDate) {
topDate = savedDraft.date;
} }
dialog.index = generateDialogIndex(topDate); dialog.index = generateDialogIndex(topDate);
@ -175,13 +178,34 @@ angular.module('myApp.services')
if (historiesStorage[peerID] === undefined) { if (historiesStorage[peerID] === undefined) {
var historyStorage = {count: null, history: [mid], pending: []}; var historyStorage = {count: null, history: [mid], pending: []};
historiesStorage[peerID] = historyStorage; historiesStorage[peerID] = historyStorage;
if (mergeReplyKeyboard(historyStorage, message)) {
$rootScope.$broadcast('history_reply_markup', {peerID: peerID});
}
} }
NotificationsManager.savePeerSettings(peerID, dialog.notify_settings); NotificationsManager.savePeerSettings(peerID, dialog.notify_settings);
if (dialog.pts) { if (channelID && dialog.pts) {
ApiUpdatesManager.addChannelState(channelID, dialog.pts); ApiUpdatesManager.addChannelState(channelID, dialog.pts);
} }
if (
!channelID &&
dialog.unread_count > 0 &&
maxSeenID &&
dialog.top_message > maxSeenID
) {
var notifyPeer = message.flags & 16 ? message.from_id : peerID;
if (message.pFlags.unread &&
!message.pFlags.out &&
!message.pFlags.silent) {
NotificationsManager.getPeerMuted(notifyPeer).then(function (muted) {
if (!muted) {
notifyAboutMessage(message);
}
});
}
}
} }
function getTopMessages (limit) { function getTopMessages (limit) {
@ -191,11 +215,11 @@ angular.module('myApp.services')
var offsetID = 0; var offsetID = 0;
var offsetPeerID = 0; var offsetPeerID = 0;
if (dialogsOffsetDate) { if (dialogsOffsetDate) {
offsetDate = dialogsOffsetDate + serverTimeOffset; offsetDate = dialogsOffsetDate + ServerTimeManager.serverTimeOffset;
} }
return MtpApiManager.invokeApi('messages.getDialogs', { return MtpApiManager.invokeApi('messages.getDialogs', {
offset_date: offsetDate, offset_date: offsetDate,
offset_id: getMessageLocalID(offsetID), offset_id: AppMessagesIDsManager.getMessageLocalID(offsetID),
offset_peer: AppPeersManager.getInputPeerByID(offsetPeerID), offset_peer: AppPeersManager.getInputPeerByID(offsetPeerID),
limit: limit limit: limit
}, { }, {
@ -211,62 +235,12 @@ angular.module('myApp.services')
var maxSeenIdIncremented = offsetDate ? true : false; var maxSeenIdIncremented = offsetDate ? true : false;
angular.forEach(dialogsResult.dialogs, function (dialog) { angular.forEach(dialogsResult.dialogs, function (dialog) {
var peerID = AppPeersManager.getPeerID(dialog.peer); saveConversation(dialog);
if (dialog.pts) {
var channelID = -peerID;
saveChannelDialog(channelID, dialog);
ApiUpdatesManager.addChannelState(channelID, dialog.pts);
} else {
if (peerID < 0) {
var chat = AppChatsManager.getChat(-peerID);
if (chat && chat.migrated_to && chat.pFlags.deactivated) {
var migratedToPeer = AppPeersManager.getPeerID(chat.migrated_to);
migratedFromTo[peerID] = migratedToPeer;
migratedToFrom[migratedToPeer] = peerID;
return;
}
}
var peerText = AppPeersManager.getPeerSearchText(peerID);
SearchIndexManager.indexObject(peerID, peerText, dialogsIndex);
var message = getMessage(dialog.top_message);
dialog.index = generateDialogIndex(message.date);
dialog.peerID = peerID;
pushDialogToStorage(dialog, message.date); if (!maxSeenIdIncremented &&
!AppPeersManager.isChannel(AppPeersManager.getPeerID(dialog.peer))) {
if (!maxSeenIdIncremented) { incrementMaxSeenID(dialog.top_message);
incrementMaxSeenID(dialog.top_message); maxSeenIdIncremented = true;
maxSeenIdIncremented = true;
}
if (historiesStorage[peerID] === undefined) {
var historyStorage = {count: null, history: [dialog.top_message], pending: []};
historiesStorage[peerID] = historyStorage;
if (mergeReplyKeyboard(historyStorage, message)) {
$rootScope.$broadcast('history_reply_markup', {peerID: peerID});
}
}
NotificationsManager.savePeerSettings(peerID, dialog.notify_settings);
if (
dialog.unread_count > 0 &&
maxSeenID &&
dialog.top_message > maxSeenID
) {
var notifyPeer = message.flags & 16 ? message.from_id : peerID;
if (message.pFlags.unread &&
!message.pFlags.out &&
!message.pFlags.silent) {
NotificationsManager.getPeerMuted(notifyPeer).then(function (muted) {
if (!muted) {
notifyAboutMessage(message);
}
});
}
}
} }
}); });
@ -280,7 +254,7 @@ angular.module('myApp.services')
function generateDialogIndex (date) { function generateDialogIndex (date) {
if (date === undefined) { if (date === undefined) {
date = tsNow(true) + serverTimeOffset; date = tsNow(true) + ServerTimeManager.serverTimeOffset;
} }
return (date * 0x10000) + ((++dialogsNum) & 0xFFFF); return (date * 0x10000) + ((++dialogsNum) & 0xFFFF);
} }
@ -319,7 +293,7 @@ angular.module('myApp.services')
return MtpApiManager.invokeApi('messages.getHistory', { return MtpApiManager.invokeApi('messages.getHistory', {
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
offset_id: maxID ? getMessageLocalID(maxID) : 0, offset_id: maxID ? AppMessagesIDsManager.getMessageLocalID(maxID) : 0,
add_offset: offset || 0, add_offset: offset || 0,
limit: limit || 0 limit: limit || 0
}, { }, {
@ -353,7 +327,7 @@ angular.module('myApp.services')
to_id: AppPeersManager.getOutputPeer(peerID), to_id: AppPeersManager.getOutputPeer(peerID),
flags: 0, flags: 0,
pFlags: {}, pFlags: {},
date: tsNow(true) + serverTimeOffset, date: tsNow(true) + ServerTimeManager.serverTimeOffset,
action: { action: {
_: 'messageActionBotIntro', _: 'messageActionBotIntro',
description: description description: description
@ -387,65 +361,6 @@ angular.module('myApp.services')
}); });
} }
var channelLocals = {};
var channelsByLocals = {};
var channelCurLocal = 0;
var fullMsgIDModulus = 4294967296;
function getFullMessageID (msgID, channelID) {
if (!channelID || msgID <= 0) {
return msgID;
}
msgID = getMessageLocalID(msgID);
var localStart = channelLocals[channelID];
if (!localStart) {
localStart = (++channelCurLocal) * fullMsgIDModulus;
channelsByLocals[localStart] = channelID;
channelLocals[channelID] = localStart;
}
return localStart + msgID;
}
function getMessageIDInfo (fullMsgID) {
if (fullMsgID < fullMsgIDModulus) {
return [fullMsgID, 0];
}
var msgID = fullMsgID % fullMsgIDModulus;
var channelID = channelsByLocals[fullMsgID - msgID];
return [msgID, channelID];
}
function getMessageLocalID (fullMsgID) {
if (!fullMsgID) {
return 0;
}
return fullMsgID % fullMsgIDModulus;
}
function splitMessageIDsByChannels (mids) {
var msgIDsByChannels = {};
var midsByChannels = {};
var i, mid, msgChannel, channelID;
for (i = 0; i < mids.length; i++) {
mid = mids[i];
msgChannel = getMessageIDInfo(mid);
channelID = msgChannel[1];
if (msgIDsByChannels[channelID] === undefined) {
msgIDsByChannels[channelID] = [];
midsByChannels[channelID] = [];
}
msgIDsByChannels[channelID].push(msgChannel[0]);
midsByChannels[channelID].push(mid);
}
return {
msgIDs: msgIDsByChannels,
mids: midsByChannels
};
}
function fillHistoryStorage (peerID, maxID, fullLimit, historyStorage) { function fillHistoryStorage (peerID, maxID, fullLimit, historyStorage) {
// console.log('fill history storage', peerID, maxID, fullLimit, angular.copy(historyStorage)); // console.log('fill history storage', peerID, maxID, fullLimit, angular.copy(historyStorage));
var offset = (migratedFromTo[peerID] && !maxID) ? 1 : 0; var offset = (migratedFromTo[peerID] && !maxID) ? 1 : 0;
@ -885,7 +800,7 @@ angular.module('myApp.services')
var offsetMessage = maxID && getMessage(maxID); var offsetMessage = maxID && getMessage(maxID);
if (offsetMessage && offsetMessage.date) { if (offsetMessage && offsetMessage.date) {
offsetDate = offsetMessage.date + serverTimeOffset; offsetDate = offsetMessage.date + ServerTimeManager.serverTimeOffset;
offsetID = offsetMessage.id; offsetID = offsetMessage.id;
offsetPeerID = getMessagePeer(offsetMessage); offsetPeerID = getMessagePeer(offsetMessage);
} }
@ -893,7 +808,7 @@ angular.module('myApp.services')
q: query, q: query,
offset_date: offsetDate, offset_date: offsetDate,
offset_peer: AppPeersManager.getInputPeerByID(offsetPeerID), offset_peer: AppPeersManager.getInputPeerByID(offsetPeerID),
offset_id: getMessageLocalID(offsetID), offset_id: AppMessagesIDsManager.getMessageLocalID(offsetID),
limit: limit || 20 limit: limit || 20
}, { }, {
timeout: 300, timeout: 300,
@ -941,7 +856,7 @@ angular.module('myApp.services')
} }
function deleteMessages (messageIDs) { function deleteMessages (messageIDs) {
var splitted = splitMessageIDsByChannels(messageIDs); var splitted = AppMessagesIDsManager.splitMessageIDsByChannels(messageIDs);
var promises = []; var promises = [];
angular.forEach(splitted.msgIDs, function (msgIDs, channelID) { angular.forEach(splitted.msgIDs, function (msgIDs, channelID) {
var promise; var promise;
@ -999,7 +914,7 @@ angular.module('myApp.services')
} }
function getMessageShareLink(fullMsgID) { function getMessageShareLink(fullMsgID) {
var info = getMessageIDInfo(fullMsgID); var info = AppMessagesIDsManager.getMessageIDInfo(fullMsgID);
var msgID = info[0]; var msgID = info[0];
var channelID = info[1]; var channelID = info[1];
if (!channelID) { if (!channelID) {
@ -1176,11 +1091,11 @@ angular.module('myApp.services')
var channelID = isChannel ? -toPeerID : 0; var channelID = isChannel ? -toPeerID : 0;
var isBroadcast = isChannel && !AppChatsManager.isMegagroup(channelID); var isBroadcast = isChannel && !AppChatsManager.isMegagroup(channelID);
var mid = getFullMessageID(apiMessage.id, channelID); var mid = AppMessagesIDsManager.getFullMessageID(apiMessage.id, channelID);
apiMessage.mid = mid; apiMessage.mid = mid;
var dialog = getDialogByPeerID(toPeerID)[0]; var dialog = getDialogByPeerID(toPeerID)[0];
if (dialog) { if (dialog && mid > 0) {
var dialogKey = apiMessage.pFlags.out var dialogKey = apiMessage.pFlags.out
? 'read_outbox_max_id' ? 'read_outbox_max_id'
: 'read_inbox_max_id'; : 'read_inbox_max_id';
@ -1192,15 +1107,15 @@ angular.module('myApp.services')
} }
if (apiMessage.reply_to_msg_id) { if (apiMessage.reply_to_msg_id) {
apiMessage.reply_to_mid = getFullMessageID(apiMessage.reply_to_msg_id, channelID); apiMessage.reply_to_mid = AppMessagesIDsManager.getFullMessageID(apiMessage.reply_to_msg_id, channelID);
} }
apiMessage.date -= serverTimeOffset; apiMessage.date -= ServerTimeManager.serverTimeOffset;
var fwdHeader = apiMessage.fwd_from; var fwdHeader = apiMessage.fwd_from;
if (fwdHeader) { if (fwdHeader) {
apiMessage.fwdFromID = fwdHeader.channel_id ? -fwdHeader.channel_id : fwdHeader.from_id; apiMessage.fwdFromID = fwdHeader.channel_id ? -fwdHeader.channel_id : fwdHeader.from_id;
fwdHeader.date -= serverTimeOffset; fwdHeader.date -= ServerTimeManager.serverTimeOffset;
} }
apiMessage.toID = toPeerID; apiMessage.toID = toPeerID;
@ -1358,7 +1273,7 @@ angular.module('myApp.services')
to_id: AppPeersManager.getOutputPeer(peerID), to_id: AppPeersManager.getOutputPeer(peerID),
flags: flags, flags: flags,
pFlags: pFlags, pFlags: pFlags,
date: tsNow(true) + serverTimeOffset, date: tsNow(true) + ServerTimeManager.serverTimeOffset,
message: text, message: text,
random_id: randomIDS, random_id: randomIDS,
reply_to_msg_id: replyToMsgID, reply_to_msg_id: replyToMsgID,
@ -1398,13 +1313,16 @@ angular.module('myApp.services')
if (asChannel) { if (asChannel) {
flags |= 16; flags |= 16;
} }
if (options.clearDraft) {
flags |= 128;
}
var apiPromise; var apiPromise;
if (options.viaBotID) { if (options.viaBotID) {
apiPromise = MtpApiManager.invokeApi('messages.sendInlineBotResult', { apiPromise = MtpApiManager.invokeApi('messages.sendInlineBotResult', {
flags: flags, flags: flags,
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
random_id: randomID, random_id: randomID,
reply_to_msg_id: getMessageLocalID(replyToMsgID), reply_to_msg_id: AppMessagesIDsManager.getMessageLocalID(replyToMsgID),
query_id: options.queryID, query_id: options.queryID,
id: options.resultID id: options.resultID
}, sentRequestOptions); }, sentRequestOptions);
@ -1417,7 +1335,7 @@ angular.module('myApp.services')
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
message: text, message: text,
random_id: randomID, random_id: randomID,
reply_to_msg_id: getMessageLocalID(replyToMsgID), reply_to_msg_id: AppMessagesIDsManager.getMessageLocalID(replyToMsgID),
entities: entities entities: entities
}, sentRequestOptions) }, sentRequestOptions)
} }
@ -1467,6 +1385,15 @@ angular.module('myApp.services')
// message.send(); // message.send();
// }, 5000); // }, 5000);
ApiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateDraftMessage',
peer: AppPeersManager.getOutputPeer(peerID),
draft: {_: 'draftMessageEmpty'}
}
});
pendingByRandomID[randomIDS] = [peerID, messageID]; pendingByRandomID[randomIDS] = [peerID, messageID];
}; };
@ -1539,7 +1466,7 @@ angular.module('myApp.services')
to_id: AppPeersManager.getOutputPeer(peerID), to_id: AppPeersManager.getOutputPeer(peerID),
flags: flags, flags: flags,
pFlags: pFlags, pFlags: pFlags,
date: tsNow(true) + serverTimeOffset, date: tsNow(true) + ServerTimeManager.serverTimeOffset,
message: '', message: '',
media: media, media: media,
random_id: randomIDS, random_id: randomIDS,
@ -1603,7 +1530,7 @@ angular.module('myApp.services')
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
media: inputMedia, media: inputMedia,
random_id: randomID, random_id: randomID,
reply_to_msg_id: getMessageLocalID(replyToMsgID) reply_to_msg_id: AppMessagesIDsManager.getMessageLocalID(replyToMsgID)
}).then(function (updates) { }).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates); ApiUpdatesManager.processUpdateMessage(updates);
}, function (error) { }, function (error) {
@ -1758,7 +1685,7 @@ angular.module('myApp.services')
to_id: AppPeersManager.getOutputPeer(peerID), to_id: AppPeersManager.getOutputPeer(peerID),
flags: flags, flags: flags,
pFlags: pFlags, pFlags: pFlags,
date: tsNow(true) + serverTimeOffset, date: tsNow(true) + ServerTimeManager.serverTimeOffset,
message: '', message: '',
media: media, media: media,
random_id: randomIDS, random_id: randomIDS,
@ -1805,7 +1732,7 @@ angular.module('myApp.services')
flags: flags, flags: flags,
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
random_id: randomID, random_id: randomID,
reply_to_msg_id: getMessageLocalID(replyToMsgID), reply_to_msg_id: AppMessagesIDsManager.getMessageLocalID(replyToMsgID),
query_id: options.queryID, query_id: options.queryID,
id: options.resultID id: options.resultID
}, sentRequestOptions); }, sentRequestOptions);
@ -1815,7 +1742,7 @@ angular.module('myApp.services')
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
media: inputMedia, media: inputMedia,
random_id: randomID, random_id: randomID,
reply_to_msg_id: getMessageLocalID(replyToMsgID) reply_to_msg_id: AppMessagesIDsManager.getMessageLocalID(replyToMsgID)
}, sentRequestOptions); }, sentRequestOptions);
} }
apiPromise.then(function (updates) { apiPromise.then(function (updates) {
@ -1851,7 +1778,7 @@ angular.module('myApp.services')
flags |= 16; flags |= 16;
} }
var splitted = splitMessageIDsByChannels(mids); var splitted = AppMessagesIDsManager.splitMessageIDsByChannels(mids);
var promises = []; var promises = [];
angular.forEach(splitted.msgIDs, function (msgIDs, channelID) { angular.forEach(splitted.msgIDs, function (msgIDs, channelID) {
var len = msgIDs.length; var len = msgIDs.length;
@ -2008,50 +1935,6 @@ angular.module('myApp.services')
return false; return false;
} }
function openChatInviteLink (hash) {
return MtpApiManager.invokeApi('messages.checkChatInvite', {
hash: hash
}).then(function (chatInvite) {
var chatTitle;
if (chatInvite._ == 'chatInviteAlready') {
AppChatsManager.saveApiChat(chatInvite.chat);
if (!chatInvite.chat.pFlags.left) {
return $rootScope.$broadcast('history_focus', {
peerString: AppChatsManager.getChatString(chatInvite.chat.id)
});
}
chatTitle = chatInvite.chat.title;
} else {
chatTitle = chatInvite.title;
}
ErrorService.confirm({
type: (chatInvite.pFlags.channel && !chatInvite.pFlags.megagroup) ? 'JOIN_CHANNEL_BY_LINK' : 'JOIN_GROUP_BY_LINK',
title: chatTitle
}).then(function () {
return MtpApiManager.invokeApi('messages.importChatInvite', {
hash: hash
}).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates);
if (updates.chats && updates.chats.length == 1) {
$rootScope.$broadcast('history_focus', {peerString: AppChatsManager.getChatString(updates.chats[0].id)
});
}
else if (updates.updates && updates.updates.length) {
for (var i = 0, len = updates.updates.length, update; i < len; i++) {
update = updates.updates[i];
if (update._ == 'updateNewMessage') {
$rootScope.$broadcast('history_focus', {peerString: AppChatsManager.getChatString(update.message.to_id.chat_id)
});
break;
}
}
}
});
});
});
}
function getMessagePeer (message) { function getMessagePeer (message) {
var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0; var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0;
@ -2100,12 +1983,26 @@ angular.module('myApp.services')
message.dateText = dateOrTimeFilter(message.date); message.dateText = dateOrTimeFilter(message.date);
if (useCache) { if (useCache) {
message.draft = DraftsManager.getServerDraft(message.peerID);
messagesForDialogs[msgID] = message; messagesForDialogs[msgID] = message;
} }
return message; return message;
} }
function wrapSingleMessage(msgID) {
if (messagesStorage[msgID]) {
return wrapForDialog(msgID);
}
if (needSingleMessages.indexOf(msgID) == -1) {
needSingleMessages.push(msgID);
if (fetchSingleMessagesTimeout === false) {
fetchSingleMessagesTimeout = setTimeout(fetchSingleMessages, 100);
}
}
return {mid: msgID, loading: true};
}
function clearDialogCache (msgID) { function clearDialogCache (msgID) {
delete messagesForDialogs[msgID]; delete messagesForDialogs[msgID];
} }
@ -2206,17 +2103,7 @@ angular.module('myApp.services')
var replyToMsgID = message.reply_to_mid; var replyToMsgID = message.reply_to_mid;
if (replyToMsgID) { if (replyToMsgID) {
if (messagesStorage[replyToMsgID]) { message.reply_to_msg = wrapSingleMessage(replyToMsgID);
message.reply_to_msg = wrapForDialog(replyToMsgID);
} else {
message.reply_to_msg = {mid: replyToMsgID, loading: true};
if (needSingleMessages.indexOf(replyToMsgID) == -1) {
needSingleMessages.push(replyToMsgID);
if (fetchSingleMessagesTimeout === false) {
fetchSingleMessagesTimeout = setTimeout(fetchSingleMessages, 100);
}
}
}
} }
return messagesForHistory[msgID] = message; return messagesForHistory[msgID] = message;
@ -2261,7 +2148,7 @@ angular.module('myApp.services')
var mids = needSingleMessages.slice(); var mids = needSingleMessages.slice();
needSingleMessages = []; needSingleMessages = [];
var splitted = splitMessageIDsByChannels(mids); var splitted = AppMessagesIDsManager.splitMessageIDsByChannels(mids);
angular.forEach(splitted.msgIDs, function (msgIDs, channelID) { angular.forEach(splitted.msgIDs, function (msgIDs, channelID) {
var promise; var promise;
if (channelID > 0) { if (channelID > 0) {
@ -2296,7 +2183,7 @@ angular.module('myApp.services')
var mids = needIncrementMessageViews.slice(); var mids = needIncrementMessageViews.slice();
needIncrementMessageViews = []; needIncrementMessageViews = [];
var splitted = splitMessageIDsByChannels(mids); var splitted = AppMessagesIDsManager.splitMessageIDsByChannels(mids);
angular.forEach(splitted.msgIDs, function (msgIDs, channelID) { angular.forEach(splitted.msgIDs, function (msgIDs, channelID) {
// console.log('increment', msgIDs, channelID); // console.log('increment', msgIDs, channelID);
MtpApiManager.invokeApi('messages.getMessagesViews', { MtpApiManager.invokeApi('messages.getMessagesViews', {
@ -2345,7 +2232,7 @@ angular.module('myApp.services')
for (i = start; i < end; i++) { for (i = start; i < end; i++) {
curMessage = history[i]; curMessage = history[i];
curDay = Math.floor((curMessage.date + midnightOffset) / 86400); curDay = Math.floor((curMessage.date + ServerTimeManager.midnightOffset) / 86400);
prevGrouped = prevMessage && prevMessage.grouped; prevGrouped = prevMessage && prevMessage.grouped;
curGrouped = curMessage.grouped; curGrouped = curMessage.grouped;
@ -2695,7 +2582,7 @@ angular.module('myApp.services')
if (pendingData) { if (pendingData) {
var peerID = pendingData[0]; var peerID = pendingData[0];
var channelID = AppPeersManager.isChannel(peerID) ? -peerID : 0; var channelID = AppPeersManager.isChannel(peerID) ? -peerID : 0;
pendingByMessageID[getFullMessageID(update.id, channelID)] = randomID; pendingByMessageID[AppMessagesIDsManager.getFullMessageID(update.id, channelID)] = randomID;
} }
break; break;
@ -2827,7 +2714,7 @@ angular.module('myApp.services')
var message = update.message; var message = update.message;
var peerID = getMessagePeer(message); var peerID = getMessagePeer(message);
var channelID = message.to_id._ == 'peerChannel' ? -peerID : 0; var channelID = message.to_id._ == 'peerChannel' ? -peerID : 0;
var mid = getFullMessageID(message.id, channelID); var mid = AppMessagesIDsManager.getFullMessageID(message.id, channelID);
if (messagesStorage[mid] === undefined) { if (messagesStorage[mid] === undefined) {
break; break;
} }
@ -2856,7 +2743,7 @@ angular.module('myApp.services')
case 'updateReadChannelOutbox': case 'updateReadChannelOutbox':
var isOut = update._ == 'updateReadHistoryOutbox' || update._ == 'updateReadChannelOutbox'; var isOut = update._ == 'updateReadHistoryOutbox' || update._ == 'updateReadChannelOutbox';
var channelID = update.channel_id; var channelID = update.channel_id;
var maxID = getFullMessageID(update.max_id, channelID); var maxID = AppMessagesIDsManager.getFullMessageID(update.max_id, channelID);
var peerID = channelID ? -channelID : AppPeersManager.getPeerID(update.peer); var peerID = channelID ? -channelID : AppPeersManager.getPeerID(update.peer);
var foundDialog = getDialogByPeerID(peerID); var foundDialog = getDialogByPeerID(peerID);
var history = (historiesStorage[peerID] || {}).history || []; var history = (historiesStorage[peerID] || {}).history || [];
@ -2902,12 +2789,14 @@ angular.module('myApp.services')
} }
} }
} }
if (!isOut && foundDialog[0]) { if (foundDialog[0]) {
if (newUnreadCount && if (!isOut &&
newUnreadCount &&
foundDialog[0].top_message <= maxID) { foundDialog[0].top_message <= maxID) {
newUnreadCount = foundDialog[0].unread_count = 0; newUnreadCount = foundDialog[0].unread_count = 0;
} }
foundDialog[0].read_inbox_max_id = maxID; var dialogKey = isOut ? 'read_outbox_max_id' : 'read_inbox_max_id';
foundDialog[0][dialogKey] = maxID;
} }
if (newUnreadCount !== false) { if (newUnreadCount !== false) {
@ -2942,7 +2831,7 @@ angular.module('myApp.services')
var peerMessagesToHandle, peerMessagesHandlePos; var peerMessagesToHandle, peerMessagesHandlePos;
for (i = 0; i < update.messages.length; i++) { for (i = 0; i < update.messages.length; i++) {
messageID = getFullMessageID(update.messages[i], channelID); messageID = AppMessagesIDsManager.getFullMessageID(update.messages[i], channelID);
message = messagesStorage[messageID]; message = messagesStorage[messageID];
if (message) { if (message) {
peerID = getMessagePeer(message); peerID = getMessagePeer(message);
@ -3043,7 +2932,7 @@ angular.module('myApp.services')
} }
if (hasDialog != needDialog) { if (hasDialog != needDialog) {
if (needDialog) { if (needDialog) {
reloadChannelDialog(channelID); reloadConversation(-channelID);
} else { } else {
if (foundDialog[0]) { if (foundDialog[0]) {
dialogsStorage.dialogs.splice(foundDialog[1], 1); dialogsStorage.dialogs.splice(foundDialog[1], 1);
@ -3061,14 +2950,14 @@ angular.module('myApp.services')
dialogsStorage.dialogs.splice(foundDialog[1], 1); dialogsStorage.dialogs.splice(foundDialog[1], 1);
} }
delete historiesStorage[peerID]; delete historiesStorage[peerID];
reloadChannelDialog(channelID).then(function () { reloadConversation(-channelID).then(function () {
$rootScope.$broadcast('history_reload', peerID); $rootScope.$broadcast('history_reload', peerID);
}); });
break; break;
case 'updateChannelMessageViews': case 'updateChannelMessageViews':
var views = update.views; var views = update.views;
var mid = getFullMessageID(update.id, update.channel_id); var mid = AppMessagesIDsManager.getFullMessageID(update.id, update.channel_id);
var message = getMessage(mid); var message = getMessage(mid);
if (message && message.views && message.views < views) { if (message && message.views && message.views < views) {
message.views = views; message.views = views;
@ -3078,31 +2967,41 @@ angular.module('myApp.services')
}); });
} }
break; break;
case 'updateDraftMessage':
var peerID = AppPeersManager.getPeerID(update.peer);
var draft = DraftsManager.saveDraft(peerID, update.draft);
var dialog = getDialogByPeerID(peerID)[0];
if (dialog) {
if (dialog && draft && draft.date) {
dialog.index = generateDialogIndex(draft.date);
pushDialogToStorage(dialog);
}
$rootScope.$broadcast('dialog_draft', {
peerID: peerID,
draft: draft
});
}
break;
} }
}); });
function reloadChannelDialog (channelID) { function reloadConversation (peerID) {
var peerID = -channelID; return MtpApiManager.invokeApi('messages.getPeerDialogs', {
return $q.all([ peers: [
AppProfileManager.getChannelFull(channelID, true), AppPeersManager.getInputPeerByID(peerID)
getHistory(peerID, 0) ]
]).then(function (results) { }).then(function (dialogsResult) {
var channelResult = results[0]; AppUsersManager.saveApiUsers(dialogsResult.users);
var historyResult = results[1]; AppChatsManager.saveApiChats(dialogsResult.chats);
var topMsgID = historyResult.history[0]; saveMessages(dialogsResult.messages);
var dialog = {
_: 'dialog',
peer: AppPeersManager.getOutputPeer(peerID),
top_message: topMsgID,
read_inbox_max_id: channelResult.read_inbox_max_id,
read_outbox_max_id: channelResult.read_outbox_max_id,
unread_count: channelResult.unread_count,
notify_settings: channelResult.notify_settings
};
saveChannelDialog(channelID, dialog);
var updatedDialogs = {}; var updatedDialogs = {};
updatedDialogs[peerID] = dialog; angular.forEach(dialogsResult.dialogs, function (dialog) {
saveConversation(dialog);
updatedDialogs[dialog.peerID] = dialog;
});
$rootScope.$broadcast('dialogs_multiupdate', updatedDialogs); $rootScope.$broadcast('dialogs_multiupdate', updatedDialogs);
}); });
} }
@ -3119,33 +3018,111 @@ angular.module('myApp.services')
}) })
}); });
$rootScope.$on('draft_updated', function (e, eventData) {
var peerID = eventData.peerID;
var draft = eventData.draft;
var dialog = getDialogByPeerID(peerID)[0];
if (dialog) {
if (dialog && draft && draft.date) {
dialog.index = generateDialogIndex(draft.date);
pushDialogToStorage(dialog);
}
}
});
return { return {
getConversations: getConversations, getConversations: getConversations,
getHistory: getHistory, getHistory: getHistory,
getSearch: getSearch, getSearch: getSearch,
getMessage: getMessage, getMessage: getMessage,
getMessageLocalID: getMessageLocalID,
getReplyKeyboard: getReplyKeyboard, getReplyKeyboard: getReplyKeyboard,
readHistory: readHistory, readHistory: readHistory,
readMessages: readMessages, readMessages: readMessages,
flushHistory: flushHistory, flushHistory: flushHistory,
deleteMessages: deleteMessages, deleteMessages: deleteMessages,
saveMessages: saveMessages,
sendText: sendText, sendText: sendText,
sendFile: sendFile, sendFile: sendFile,
sendOther: sendOther, sendOther: sendOther,
forwardMessages: forwardMessages, forwardMessages: forwardMessages,
startBot: startBot, startBot: startBot,
openChatInviteLink: openChatInviteLink,
convertMigratedPeer: convertMigratedPeer, convertMigratedPeer: convertMigratedPeer,
getMessagePeer: getMessagePeer, getMessagePeer: getMessagePeer,
getFullMessageID: getFullMessageID,
getMessageThumb: getMessageThumb, getMessageThumb: getMessageThumb,
getMessageShareLink: getMessageShareLink, getMessageShareLink: getMessageShareLink,
clearDialogCache: clearDialogCache, clearDialogCache: clearDialogCache,
wrapForDialog: wrapForDialog, wrapForDialog: wrapForDialog,
wrapForHistory: wrapForHistory, wrapForHistory: wrapForHistory,
wrapReplyMarkup: wrapReplyMarkup, wrapReplyMarkup: wrapReplyMarkup,
wrapSingleMessage: wrapSingleMessage,
regroupWrappedHistory: regroupWrappedHistory regroupWrappedHistory: regroupWrappedHistory
} }
}); })
.service('AppMessagesIDsManager', function () {
var channelLocals = {};
var channelsByLocals = {};
var channelCurLocal = 0;
var fullMsgIDModulus = 4294967296;
return {
getFullMessageID: getFullMessageID,
getMessageIDInfo: getMessageIDInfo,
getMessageLocalID: getMessageLocalID,
splitMessageIDsByChannels: splitMessageIDsByChannels,
}
function getFullMessageID (msgID, channelID) {
if (!channelID || msgID <= 0) {
return msgID;
}
msgID = getMessageLocalID(msgID);
var localStart = channelLocals[channelID];
if (!localStart) {
localStart = (++channelCurLocal) * fullMsgIDModulus;
channelsByLocals[localStart] = channelID;
channelLocals[channelID] = localStart;
}
return localStart + msgID;
}
function getMessageIDInfo (fullMsgID) {
if (fullMsgID < fullMsgIDModulus) {
return [fullMsgID, 0];
}
var msgID = fullMsgID % fullMsgIDModulus;
var channelID = channelsByLocals[fullMsgID - msgID];
return [msgID, channelID];
}
function getMessageLocalID (fullMsgID) {
if (!fullMsgID) {
return 0;
}
return fullMsgID % fullMsgIDModulus;
}
function splitMessageIDsByChannels (mids) {
var msgIDsByChannels = {};
var midsByChannels = {};
var i, mid, msgChannel, channelID;
for (i = 0; i < mids.length; i++) {
mid = mids[i];
msgChannel = getMessageIDInfo(mid);
channelID = msgChannel[1];
if (msgIDsByChannels[channelID] === undefined) {
msgIDsByChannels[channelID] = [];
midsByChannels[channelID] = [];
}
msgIDsByChannels[channelID].push(msgChannel[0]);
midsByChannels[channelID].push(mid);
}
return {
msgIDs: msgIDsByChannels,
mids: midsByChannels
};
}
})

259
app/js/services.js

@ -11,7 +11,7 @@
angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, qSync, MtpApiManager, RichTextProcessor, Storage, _) { .service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, qSync, MtpApiManager, RichTextProcessor, ServerTimeManager, Storage, _) {
var users = {}, var users = {},
usernames = {}, usernames = {},
userAccess = {}, userAccess = {},
@ -19,14 +19,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
contactsFillPromise, contactsFillPromise,
contactsList, contactsList,
contactsIndex = SearchIndexManager.createIndex(), contactsIndex = SearchIndexManager.createIndex(),
myID, myID;
serverTimeOffset = 0;
Storage.get('server_time_offset').then(function (to) {
if (to) {
serverTimeOffset = to;
}
});
MtpApiManager.getUserID().then(function (id) { MtpApiManager.getUserID().then(function (id) {
myID = id; myID = id;
}); });
@ -134,10 +128,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
if (apiUser.status) { if (apiUser.status) {
if (apiUser.status.expires) { if (apiUser.status.expires) {
apiUser.status.expires -= serverTimeOffset; apiUser.status.expires -= ServerTimeManager.serverTimeOffset;
} }
if (apiUser.status.was_online) { if (apiUser.status.was_online) {
apiUser.status.was_online -= serverTimeOffset; apiUser.status.was_online -= ServerTimeManager.serverTimeOffset;
} }
} }
if (apiUser.pFlags.bot) { if (apiUser.pFlags.bot) {
@ -428,10 +422,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
user.status = update.status; user.status = update.status;
if (user.status) { if (user.status) {
if (user.status.expires) { if (user.status.expires) {
user.status.expires -= serverTimeOffset; user.status.expires -= ServerTimeManager.serverTimeOffset;
} }
if (user.status.was_online) { if (user.status.was_online) {
user.status.was_online -= serverTimeOffset; user.status.was_online -= ServerTimeManager.serverTimeOffset;
} }
} }
user.sortStatus = getUserStatusForSort(user.status); user.sortStatus = getUserStatusForSort(user.status);
@ -2660,7 +2654,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
return MtpApiManager.invokeApi('messages.getBotCallbackAnswer', { return MtpApiManager.invokeApi('messages.getBotCallbackAnswer', {
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
msg_id: AppMessagesManager.getMessageLocalID(id), msg_id: AppMessagesIDsManager.getMessageLocalID(id),
data: button.data data: button.data
}, {timeout: 1, stopTime: -1, noErrorBox: true}).then(function (callbackAnswer) { }, {timeout: 1, stopTime: -1, noErrorBox: true}).then(function (callbackAnswer) {
if (typeof callbackAnswer.message != 'string' || if (typeof callbackAnswer.message != 'string' ||
@ -4218,7 +4212,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}) })
.service('LocationParamsService', function (qSync, $rootScope, $routeParams, AppPeersManager, AppUsersManager, AppMessagesManager, PeersSelectService, AppStickersManager, ErrorService) { .service('LocationParamsService', function (qSync, $rootScope, $routeParams, AppPeersManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppMessagesIDsManager, MtpApiManager, ApiUpdatesManager, PeersSelectService, AppStickersManager, ErrorService) {
var tgAddrRegExp = /^(web\+)?tg:(\/\/)?(.+)/; var tgAddrRegExp = /^(web\+)?tg:(\/\/)?(.+)/;
@ -4263,7 +4257,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
params.startParam = matches[3]; params.startParam = matches[3];
} }
else if (matches[2] == 'post') { else if (matches[2] == 'post') {
params.messageID = AppMessagesManager.getFullMessageID(parseInt(matches[3]), -peerID); params.messageID = AppMessagesIDsManager.getFullMessageID(parseInt(matches[3]), -peerID);
} }
$rootScope.$broadcast('history_focus', params); $rootScope.$broadcast('history_focus', params);
@ -4272,7 +4266,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
if (matches = url.match(/^join\?invite=(.+)$/)) { if (matches = url.match(/^join\?invite=(.+)$/)) {
AppMessagesManager.openChatInviteLink(matches[1]); openChatInviteLink(matches[1]);
return true; return true;
} }
@ -4444,6 +4438,52 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}); });
} }
function openChatInviteLink (hash) {
return MtpApiManager.invokeApi('messages.checkChatInvite', {
hash: hash
}).then(function (chatInvite) {
var chatTitle;
if (chatInvite._ == 'chatInviteAlready') {
AppChatsManager.saveApiChat(chatInvite.chat);
var canJump = !chatInvite.chat.pFlags.left ||
AppChatsManager.isChannel(chatInvite.chat.id) && chatInvite.chat.username;
if (canJump) {
return $rootScope.$broadcast('history_focus', {
peerString: AppChatsManager.getChatString(chatInvite.chat.id)
});
}
chatTitle = chatInvite.chat.title;
} else {
chatTitle = chatInvite.title;
}
ErrorService.confirm({
type: (chatInvite.pFlags.channel && !chatInvite.pFlags.megagroup) ? 'JOIN_CHANNEL_BY_LINK' : 'JOIN_GROUP_BY_LINK',
title: chatTitle
}).then(function () {
return MtpApiManager.invokeApi('messages.importChatInvite', {
hash: hash
}).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates);
if (updates.chats && updates.chats.length == 1) {
$rootScope.$broadcast('history_focus', {peerString: AppChatsManager.getChatString(updates.chats[0].id)
});
}
else if (updates.updates && updates.updates.length) {
for (var i = 0, len = updates.updates.length, update; i < len; i++) {
update = updates.updates[i];
if (update._ == 'updateNewMessage') {
$rootScope.$broadcast('history_focus', {peerString: AppChatsManager.getChatString(update.message.to_id.chat_id)
});
break;
}
}
}
});
});
});
}
return { return {
start: start, start: start,
shareUrl: shareUrl shareUrl: shareUrl
@ -4451,52 +4491,197 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}) })
.service('DraftsManager', function (qSync, Storage) { .service('DraftsManager', function ($rootScope, qSync, MtpApiManager, ApiUpdatesManager, AppMessagesIDsManager, AppPeersManager, RichTextProcessor, Storage, ServerTimeManager) {
var cachedServerDrafts = {};
var localDrafts = {}; $rootScope.$on('apiUpdate', function (e, update) {
if (update._ != 'updateDraftMessage') {
return;
}
var peerID = AppPeersManager.getPeerID(update.peer);
saveDraft(peerID, update.draft, true);
});
return { return {
getDraft: getDraft, getDraft: getDraft,
getServerDraft: getServerDraft,
saveDraft: saveDraft, saveDraft: saveDraft,
changeDraft: changeDraft, changeDraft: changeDraft,
syncDraft: syncDraft syncDraft: syncDraft
}; };
function getDraft (peerID, options) { function getDraft (peerID, unsyncOnly) {
console.warn(dT(), 'get draft', peerID, unsyncOnly);
return Storage.get('draft' + peerID).then(function (draft) { return Storage.get('draft' + peerID).then(function (draft) {
if (typeof draft === 'string' && draft.length > 0) { if (typeof draft === 'string') {
draft = { if (draft.length > 0) {
text: draft draft = {
}; text: draft
};
} else {
draft = false;
}
} }
if (draft === false || draft == null) { if (!draft && !unsyncOnly) {
draft = ''; draft = getServerDraft(peerID);
console.warn(dT(), 'server', draft);
} else {
console.warn(dT(), 'local', draft);
} }
var replyToMsgID = draft && draft.replyToMsgID;
if (replyToMsgID) {
var channelID = AppPeersManager.isChannel(peerID) ? -peerID : false;
draft.replyToMsgID = AppMessagesIDsManager.getFullMessageID(replyToMsgID, channelID);
}
return draft;
}); });
} }
function saveDraft(peerID, draftData) { function getServerDraft(peerID) {
localDrafts[peerID] = draftData; var cached = cachedServerDrafts[peerID];
if (cached !== undefined) {
return cached;
}
return false;
}
function saveDraft(peerID, apiDraft, notify) {
if (notify) {
console.warn(dT(), 'save draft', peerID, apiDraft, notify);
}
var draft = processApiDraft(apiDraft);
cachedServerDrafts[peerID] = draft;
if (notify) {
changeDraft(peerID, draft);
$rootScope.$broadcast('draft_updated', {
peerID: peerID,
draft: draft
});
}
return draft;
} }
function changeDraft(peerID, message, options) { function changeDraft(peerID, draft) {
options = options || {}; console.warn(dT(), 'change draft', peerID, draft);
if (typeof message === 'string' || options.replyToMsgID) { if (!peerID) {
var localDraft = { console.trace('empty peerID');
text: message, }
replyToMsgID: replyToMsgID if (!draft) {
draft = {
text: '',
replyToMsgID: 0
}; };
}
draft.replyToMsgID = draft.replyToMsgID
? AppMessagesIDsManager.getMessageLocalID(draft.replyToMsgID)
: 0;
var draftKey = 'draft' + peerID;
if (!isEmptyDraft(draft)) {
var backupDraftObj = {}; var backupDraftObj = {};
backupDraftObj['draft' + peerID] = localDraft; backupDraftObj[draftKey] = draft;
Storage.set(backupDraftObj); Storage.set(backupDraftObj);
} else { } else {
Storage.remove('draft' + peerID); Storage.remove(draftKey);
} }
} }
function syncDraft(peerID) { function draftsAreEqual(draft1, draft2) {
var isEmpty1 = isEmptyDraft(draft1);
var isEmpty2 = isEmptyDraft(draft2);
if (isEmpty1 && isEmpty2) {
return true;
}
if (isEmpty1 != isEmpty2) {
return false;
}
if (draft1.replyToMsgID != draft2.replyToMsgID) {
return false;
}
if (draft1.text != draft2.text) {
return false;
}
return true;
}
function isEmptyDraft(draft) {
if (!draft) {
return true;
}
if (draft.replyToMsgID > 0) {
return false;
}
if (typeof draft.text !== 'string' || !draft.text.length) {
return true;
}
return false;
}
function processApiDraft(draft) {
if (!draft || draft._ != 'draftMessage') {
return false;
}
var entities = RichTextProcessor.parseEntities(draft.message);
var serverEntities = draft.entities || [];
entities = RichTextProcessor.mergeEntities(entities, serverEntities);
var text = RichTextProcessor.wrapDraftText(draft.message, {entities: entities});
var richMessage = RichTextProcessor.wrapRichText(draft.message, {noLinks: true, noLinebreaks: true});
return {
text: text,
richMessage: richMessage,
replyToMsgID: draft.reply_to_msg_id || 0,
date: draft.date - ServerTimeManager.serverTimeOffset
}
}
function syncDraft(peerID) {
console.warn(dT(), 'sync draft', peerID);
getDraft(peerID, true).then(function (localDraft) {
var serverDraft = cachedServerDrafts[peerID];
if (draftsAreEqual(serverDraft, localDraft)) {
console.warn(dT(), 'equal drafts', localDraft, serverDraft);
return;
}
console.warn(dT(), 'changed draft', localDraft, serverDraft);
var params = {
flags: 0,
peer: AppPeersManager.getInputPeerByID(peerID)
};
var draftObj;
if (isEmptyDraft(localDraft)) {
draftObj = {_: 'draftMessageEmpty'};
params.message = '';
} else {
draftObj = {_: 'draftMessage'};
var message = localDraft.text;
var entities = [];
message = RichTextProcessor.parseEmojis(message);
message = RichTextProcessor.parseMarkdown(message, entities);
if (localDraft.replyToMsgID > 0) {
params.flags |= 1;
params.reply_to_msg_id = localDraft.replyToMsgID;
draftObj.reply_to_msg_id = localDraft.replyToMsgID;
}
if (entities.length) {
params.flags |= 8;
params.entities = entities;
draftObj.entities = entities;
}
params.message = message;
draftObj.message = message;
}
MtpApiManager.invokeApi('messages.saveDraft', params).then(function () {
draftObj.date = tsNow(true) + ServerTimeManager.serverTimeOffset;
saveDraft(peerID, draftObj, true);
});
});
} }
}) })

8
app/less/app.less

@ -1310,9 +1310,12 @@ a.im_dialog {
.im_short_message_text { .im_short_message_text {
color: #808080; color: #808080;
} }
.im_dialog_draft_from {
color: #c05f5a;
}
} }
a.im_dialog:hover, a.im_dialog:hover,
a.im_dialog_selected { a.im_dialog_selected {
.im_short_message_text { .im_short_message_text {
@ -1326,7 +1329,8 @@ a.im_dialog_selected {
.im_short_message_media, .im_short_message_media,
.im_short_message_service, .im_short_message_service,
.im_short_message_text, .im_short_message_text,
.im_dialog_message { .im_dialog_message,
.im_dialog_draft_from {
color: #fff; color: #fff;
} }
} }

26
app/partials/desktop/dialog.html

@ -22,17 +22,26 @@
<span my-peer-link="dialogMessage.peerID" verified="true"></span> <span my-peer-link="dialogMessage.peerID" verified="true"></span>
</div> </div>
<div ng-if="dialogMessage.typing > 0" class="im_dialog_message im_dialog_message_typing"> <div ng-switch="dialogMessage.typing ? 'typing' : (!dialogMessage.unreadCount && dialogMessage.draft ? 'draft' : (dialogMessage.deleted ? 'deleted' : 'message'))">
<span class="im_short_message_service" my-i18n="im_conversation_group_typing">
<my-i18n-param name="name"><span my-peer-link="dialogMessage.typing" short="true" class="im_dialog_chat_from_wrap"></span></my-i18n-param><my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param>
</span>
</div>
<div class="im_dialog_message_notyping" ng-switch="dialogMessage.deleted"> <div ng-switch-when="typing" class="im_dialog_message im_dialog_message_typing">
<div ng-switch-when="true" class="im_dialog_message"> <span class="im_short_message_service" my-i18n="im_conversation_group_typing">
<my-i18n-param name="name"><span my-peer-link="dialogMessage.typing" short="true" class="im_dialog_chat_from_wrap"></span></my-i18n-param><my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param>
</span>
</div>
<div ng-switch-when="draft" class="im_dialog_message">
<span class="im_dialog_chat_from_wrap">
<span class="im_dialog_draft_from" my-i18n="conversation_draft"></span>
</span>
<span class="im_short_message_text" ng-bind-html="dialogMessage.draft.richMessage"></span>
</div>
<div ng-switch-when="deleted" class="im_dialog_message">
<span class="im_short_message_text" my-i18n="conversation_message_deleted"></span> <span class="im_short_message_text" my-i18n="conversation_message_deleted"></span>
</div> </div>
<div ng-switch-default class="im_dialog_message">
<div ng-switch-when="message" class="im_dialog_message">
<span ng-switch="dialogMessage.peerID > 0 || dialogMessage.fromID < 0"> <span ng-switch="dialogMessage.peerID > 0 || dialogMessage.fromID < 0">
<span ng-switch-when="true"> <span ng-switch-when="true">
<span class="im_dialog_chat_from_wrap" ng-if="dialogMessage.pFlags.out && dialogMessage.fromID > 0"> <span class="im_dialog_chat_from_wrap" ng-if="dialogMessage.pFlags.out && dialogMessage.fromID > 0">
@ -62,4 +71,5 @@
</div> </div>
</div> </div>
</a> </a>

3
app/partials/desktop/im.html

@ -128,6 +128,7 @@
<div my-arc-progress stroke="3" width="26"></div> <div my-arc-progress stroke="3" width="26"></div>
</div> </div>
<div ng-switch-default class="im_history_typing" ng-show="historyState.typing.length > 0 &amp;&amp; !historyFilter.mediaType &amp;&amp; !state.empty" ng-switch="historyState.typing.length" my-i18n> <div ng-switch-default class="im_history_typing" ng-show="historyState.typing.length > 0 &amp;&amp; !historyFilter.mediaType &amp;&amp; !state.empty" ng-switch="historyState.typing.length" my-i18n>
<span ng-switch-when="0"></span>
<span ng-switch-when="1" my-i18n-format="im_one_typing"></span> <span ng-switch-when="1" my-i18n-format="im_one_typing"></span>
<span ng-switch-when="2" my-i18n-format="im_two_typing"></span> <span ng-switch-when="2" my-i18n-format="im_two_typing"></span>
<span ng-switch-default my-i18n-format="im_many_typing"></span> <span ng-switch-default my-i18n-format="im_many_typing"></span>
@ -187,7 +188,7 @@
<div class="im_send_form_inline_results" my-inline-results="inlineResults"></div> <div class="im_send_form_inline_results" my-inline-results="inlineResults"></div>
<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-mousedown="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(true)"><i class="icon icon-reply-bar"></i><i class="icon icon-reply-bar"></i></a>
<a class="im_message_reply_wrap" my-reply-message="draftMessage.replyToMessage" watch="true"></a> <a class="im_message_reply_wrap" my-reply-message="draftMessage.replyToMessage" watch="true"></a>
</div> </div>

26
app/partials/mobile/dialog.html

@ -22,17 +22,26 @@
<span my-peer-link="dialogMessage.peerID" verified="true"></span> <span my-peer-link="dialogMessage.peerID" verified="true"></span>
</div> </div>
<div ng-if="dialogMessage.typing > 0" class="im_dialog_message im_dialog_message_typing"> <div ng-switch="dialogMessage.typing ? 'typing' : (!dialogMessage.unreadCount && dialogMessage.draft ? 'draft' : (dialogMessage.deleted ? 'deleted' : 'message'))">
<span class="im_short_message_service" my-i18n="im_conversation_group_typing">
<my-i18n-param name="name"><span my-peer-link="dialogMessage.typing" short="true" class="im_dialog_chat_from_wrap"></span></my-i18n-param><my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param>
</span>
</div>
<div class="im_dialog_message_notyping" ng-switch="dialogMessage.deleted"> <div ng-switch-when="typing" class="im_dialog_message im_dialog_message_typing">
<div ng-switch-when="true" class="im_dialog_message"> <span class="im_short_message_service" my-i18n="im_conversation_group_typing">
<my-i18n-param name="name"><span my-peer-link="dialogMessage.typing" short="true" class="im_dialog_chat_from_wrap"></span></my-i18n-param><my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param>
</span>
</div>
<div ng-switch-when="draft" class="im_dialog_message">
<span class="im_dialog_chat_from_wrap">
<span class="im_dialog_draft_from" my-i18n="conversation_draft"></span>
</span>
<span class="im_short_message_text" ng-bind-html="dialogMessage.draft.richMessage"></span>
</div>
<div ng-switch-when="deleted" class="im_dialog_message">
<span class="im_short_message_text" my-i18n="conversation_message_deleted"></span> <span class="im_short_message_text" my-i18n="conversation_message_deleted"></span>
</div> </div>
<div ng-switch-default class="im_dialog_message">
<div ng-switch-when="message" class="im_dialog_message">
<span ng-switch="dialogMessage.peerID > 0 || dialogMessage.fromID < 0"> <span ng-switch="dialogMessage.peerID > 0 || dialogMessage.fromID < 0">
<span ng-switch-when="true"> <span ng-switch-when="true">
<span class="im_dialog_chat_from_wrap" ng-if="dialogMessage.pFlags.out && dialogMessage.fromID > 0"> <span class="im_dialog_chat_from_wrap" ng-if="dialogMessage.pFlags.out && dialogMessage.fromID > 0">
@ -62,4 +71,5 @@
</div> </div>
</div> </div>
</a> </a>

Loading…
Cancel
Save