Browse Source

Added dialogs modal, added connection status bar, added forward button, invite users to chat, kick users from chat

master
Igor Zhukov 11 years ago
parent
commit
629d49b445
  1. 151
      app/css/app.css
  2. 230
      app/js/controllers.js
  3. 47
      app/js/directives.js
  4. 23
      app/js/lib/mtproto.js
  5. 100
      app/js/services.js
  6. 30
      app/partials/chat_create_modal.html
  7. 7
      app/partials/chat_modal.html
  8. 53
      app/partials/contacts_modal.html
  9. 2
      app/partials/dialog.html
  10. 11
      app/partials/head.html
  11. 1
      app/partials/im.html
  12. 28
      app/partials/peer_select.html

151
app/css/app.css

@ -109,6 +109,7 @@ fieldset[disabled] .btn-tg:active,
.btn-tg.disabled.active, .btn-tg.disabled.active,
.btn-tg[disabled].active, .btn-tg[disabled].active,
fieldset[disabled] .btn-tg.active { fieldset[disabled] .btn-tg.active {
color: #DDD;
background-color: #999; background-color: #999;
border-color: #999; border-color: #999;
} }
@ -145,6 +146,19 @@ fieldset[disabled] .btn-tg.active {
line-height: 0; line-height: 0;
height: auto; height: auto;
} }
.navbar-offline {
max-width: 250px;
margin: 0 auto;
float: none;
}
.navbar-offline-text {
padding: 12px 15px;
/*color: #FFF;*/
color: #b9cfe3;
font-size: 13px;
display: block;
line-height: 20px;
}
.tg_head_logo { .tg_head_logo {
@ -438,7 +452,6 @@ fieldset[disabled] .btn-tg.active {
/* Dialogs list */ /* Dialogs list */
.im_dialogs_col { .im_dialogs_col {
/*min-width: 315px;*/
margin-right: -7px; margin-right: -7px;
} }
.im_dialogs_col .nano > .pane { .im_dialogs_col .nano > .pane {
@ -588,6 +601,9 @@ a.im_dialog:hover .im_dialog_message_text {
color: #428bca; color: #428bca;
background-color: #fff; background-color: #fff;
} }
.im_dialogs_modal_col .im_dialog_badge {
display: none;
}
.im_dialog_unread { .im_dialog_unread {
background: #c1d6e5; background: #c1d6e5;
@ -649,11 +665,13 @@ a.im_dialog:hover .im_dialog_date {
.im_history_col { .im_history_col {
} }
.im_history_col .nano > .pane { .im_history_col .nano > .pane,
.contacts_modal_col .nano > .pane,
.im_dialogs_modal_col .nano > .pane {
background : rgba(3,36,64,0.08); background : rgba(3,36,64,0.08);
width : 9px; width : 9px;
top: 10px; right: 0;
right: 8px; top: 0;
-webkit-transition : .2s; -webkit-transition : .2s;
-moz-transition : .2s; -moz-transition : .2s;
-o-transition : .2s; -o-transition : .2s;
@ -662,7 +680,14 @@ a.im_dialog:hover .im_dialog_date {
-webkit-border-radius : 2px; -webkit-border-radius : 2px;
border-radius : 2px; border-radius : 2px;
} }
.im_history_col .nano > .pane > .slider {
.im_history_col .nano > .pane {
top: 10px;
right: 8px;
}
.im_history_col .nano > .pane > .slider,
.contacts_modal_col .nano > .pane > .slider,
.im_dialogs_modal_col .nano > .pane > .slider {
background : rgba(3,46,79,0.22); background : rgba(3,46,79,0.22);
margin: 0; margin: 0;
-moz-border-radius : 2px; -moz-border-radius : 2px;
@ -1103,7 +1128,8 @@ div.im_message_video_thumb {
.im_content_message_wrap { .im_content_message_wrap {
margin: 10px 0px 5px 16px; /*margin: 10px 0px 5px 16px;*/
margin: 8px 0px 8px 16px
} }
.icon-message-status { .icon-message-status {
background: #43A4DB; background: #43A4DB;
@ -1150,6 +1176,7 @@ div.im_message_video_thumb {
.im_message_date { .im_message_date {
color: #adadad; color: #adadad;
font-size: 0.85em; font-size: 0.85em;
padding-bottom: 20px;
} }
div.im_message_author, div.im_message_author,
div.im_message_body { div.im_message_body {
@ -1498,6 +1525,10 @@ img.img_fullsize {
padding: 8px 7px; padding: 8px 7px;
border-top: 1px solid #F0F0F0; border-top: 1px solid #F0F0F0;
} }
.chat_modal_participant_kick {
padding: 11px 0;
display: block;
}
.chat_modal_participant_name { .chat_modal_participant_name {
display: block; display: block;
color: #3C6E97; color: #3C6E97;
@ -1777,13 +1808,38 @@ img.img_fullsize {
opacity: 1; opacity: 1;
} }
.contacts_modal_contact_wrap { .contacts_modal_col {
padding: 8px 7px; margin-right: -17px;
border-top: 1px solid #F0F0F0; }
.contacts_scrollable_wrap {
padding: 0 17px 0 0;
outline: none ! important;
}
.contacts_modal_members_list .contacts_modal_contact_wrap {
/*margin-top: 0;*/
}
.contacts_modal_members_list a.contacts_modal_contact {
clear: both;
overflow: hidden;
color: #000;
padding: 8px 9px;
border-radius: 0;
}
.contacts_modal_members_list a.contacts_modal_contact:hover {
border-radius: 2px;
background: #f2f6fa;
} }
.contacts_modal_contact_wrap:first-child { .contacts_modal_members_list .active a.contacts_modal_contact {
border-top: 0; border-radius: 2px;
background-color: #6490b1;
} }
.contacts_modal_members_list .active a.contacts_modal_contact:hover {
background-color: #6490b1;
}
.contacts_modal_contact_name { .contacts_modal_contact_name {
display: block; display: block;
color: #3C6E97; color: #3C6E97;
@ -1793,9 +1849,6 @@ img.img_fullsize {
.non_osx .contacts_modal_contact_name { .non_osx .contacts_modal_contact_name {
font-size: 12px; font-size: 12px;
} }
.contacts_modal_contact_status {
color: #999;
}
.contacts_modal_contact_photo { .contacts_modal_contact_photo {
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -1803,6 +1856,50 @@ img.img_fullsize {
overflow: hidden; overflow: hidden;
} }
.contacts_modal_contact_status {
color: #999;
}
a.contacts_modal_contact:hover .contacts_modal_contact_status {
color: #91a6ba;
}
.contacts_modal_members_list .active .contacts_modal_contact_name,
.contacts_modal_members_list .active a.contacts_modal_contact .contacts_modal_contact_status {
color: #FFF;
}
.icon-contact-tick {
position: absolute;
right: 10px;
top: 22px;
width: 17px;
height: 15px;
/*margin: 10px 0 0 -75px;*/
background: url(../img/icons/IconsetW.png?1) -13px -343px no-repeat;
background-size: 42px 460px;
}
.is_1x .icon-contact-tick {
background-image: url(../img/icons/IconsetW_1x.png?2);
}
.contacts_modal_members_list .active .icon-contact-tick,
.contacts_modal_members_list a.contacts_modal_contact:hover .icon-contact-tick {
background-position: -13px -367px;
opacity: 0.5;
}
.contacts_modal_members_list .active .icon-contact-tick {
opacity: 1 !important;
}
.contacts_modal_panel {
padding-top: 10px;
}
.contacts_modal_actions {
padding-top: 10px;
}
/* Messages edit panel */ /* Messages edit panel */
.im_edit_panel_wrap { .im_edit_panel_wrap {
@ -1816,6 +1913,7 @@ img.img_fullsize {
width: 100%; width: 100%;
} }
.im_edit_delete_link, .im_edit_delete_link,
.im_edit_forward_link,
.im_edit_cancel_link { .im_edit_cancel_link {
display: block; display: block;
padding: 8px 17px; padding: 8px 17px;
@ -1823,11 +1921,13 @@ img.img_fullsize {
.im_edit_cancel_link { .im_edit_cancel_link {
float: left; float: left;
} }
.im_edit_delete_link { .im_edit_delete_link,
.im_edit_forward_link {
float: right; float: right;
} }
.im_edit_delete_link:hover, .im_edit_delete_link:hover,
.im_edit_forward_link:hover,
.im_edit_cancel_link:hover { .im_edit_cancel_link:hover {
background: #f2f6fa; background: #f2f6fa;
text-decoration: none; text-decoration: none;
@ -1898,8 +1998,11 @@ img.img_fullsize {
.is_1x .icon-select-tick { .is_1x .icon-select-tick {
background-image: url(../img/icons/IconsetW_1x.png?2); background-image: url(../img/icons/IconsetW_1x.png?2);
} }
.im_history_selectable .icon-select-tick {
display: inline-block; @media (min-width: 1024px) {
.im_history_selectable .icon-select-tick {
display: inline-block;
}
} }
.im_message_selected .icon-select-tick, .im_message_selected .icon-select-tick,
@ -2067,4 +2170,18 @@ img.img_fullsize {
.im_emoji_btn { .im_emoji_btn {
display: none; display: none;
} }
}
/* Dialogs modal */
.peer_select_window .modal-dialog {
max-width: 506px;
}
.peer_select_modal_wrap .modal-body {
padding: 10px 10px 15px;
}
.text-invisible {
visibility: hidden;
} }

230
app/js/controllers.js

@ -151,7 +151,7 @@ angular.module('myApp.controllers', [])
}; };
}) })
.controller('AppIMController', function ($scope, $location, $routeParams, $modal, $rootScope, $modalStack, MtpApiManager) { .controller('AppIMController', function ($scope, $location, $routeParams, $modal, $rootScope, $modalStack, MtpApiManager, AppUsersManager, ContactsSelectService) {
$scope.$on('$routeUpdate', updateCurDialog); $scope.$on('$routeUpdate', updateCurDialog);
@ -176,14 +176,35 @@ angular.module('myApp.controllers', [])
} }
$scope.openContacts = function () { $scope.openContacts = function () {
$modal.open({ ContactsSelectService.selectContact().then(function (userID) {
templateUrl: 'partials/contacts_modal.html?3', $scope.dialogSelect(AppUsersManager.getUserString(userID));
controller: 'ContactsModalController',
scope: $rootScope.$new(),
windowClass: 'contacts_modal_window'
}); });
} }
$scope.openGroup = function () {
ContactsSelectService.selectContacts().then(function (userIDs) {
if (userIDs.length == 1) {
$scope.dialogSelect(AppUsersManager.getUserString(userIDs[0]));
} else if (userIDs.length > 1) {
var scope = $rootScope.$new();
scope.userIDs = userIDs;
$modal.open({
templateUrl: 'partials/chat_create_modal.html?3',
controller: 'ChatCreateModalController',
scope: scope,
windowClass: 'contacts_modal_window'
});
}
});
}
$scope.dialogSelect = function (peerString) {
$rootScope.$broadcast('history_focus', {peerString: peerString});
};
updateCurDialog(); updateCurDialog();
function updateCurDialog() { function updateCurDialog() {
@ -300,11 +321,11 @@ angular.module('myApp.controllers', [])
$scope.$broadcast('ui_dialogs_append'); $scope.$broadcast('ui_dialogs_append');
}); });
} };
}) })
.controller('AppImHistoryController', function ($scope, $location, $timeout, $rootScope, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager, IdleManager, StatusManager) { .controller('AppImHistoryController', function ($scope, $location, $timeout, $rootScope, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager, PeersSelectService, IdleManager, StatusManager) {
$scope.$watch('curDialog.peer', applyDialogSelect); $scope.$watch('curDialog.peer', applyDialogSelect);
@ -324,6 +345,7 @@ angular.module('myApp.controllers', [])
$scope.toggleMessage = toggleMessage; $scope.toggleMessage = toggleMessage;
$scope.selectedDelete = selectedDelete; $scope.selectedDelete = selectedDelete;
$scope.selectedForward = selectedForward;
$scope.selectedCancel = selectedCancel; $scope.selectedCancel = selectedCancel;
$scope.toggleEdit = toggleEdit; $scope.toggleEdit = toggleEdit;
$scope.toggleMedia = toggleMedia; $scope.toggleMedia = toggleMedia;
@ -391,16 +413,14 @@ angular.module('myApp.controllers', [])
if (!hasMore || !offset) { if (!hasMore || !offset) {
return; return;
} }
// console.trace('load history'); console.trace('load history');
var inputMediaFilter = $scope.mediaType && {_: inputMediaFilters[$scope.mediaType]}, var inputMediaFilter = $scope.mediaType && {_: inputMediaFilters[$scope.mediaType]},
getMessagesPromise = inputMediaFilter getMessagesPromise = inputMediaFilter
? AppMessagesManager.getSearch($scope.curDialog.inputPeer, '', inputMediaFilter, maxID, startLimit) ? AppMessagesManager.getSearch($scope.curDialog.inputPeer, '', inputMediaFilter, maxID, limit)
: AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, startLimit); : AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, limit);
getMessagesPromise.then(function (historyResult) { getMessagesPromise.then(function (historyResult) {
console.log('got', maxID, historyResult);
offset += limit; offset += limit;
hasMore = offset < historyResult.count; hasMore = offset < historyResult.count;
maxID = historyResult.history[historyResult.history.length - 1]; maxID = historyResult.history[historyResult.history.length - 1];
@ -497,6 +517,24 @@ angular.module('myApp.controllers', [])
} }
} }
function selectedForward () {
if ($scope.selectedCount > 0) {
var selectedMessageIDs = [];
angular.forEach($scope.selectedMsgs, function (t, messageID) {
selectedMessageIDs.push(messageID);
});
PeersSelectService.selectPeer().then(function (peerString) {
var inputPeer = AppPeersManager.getInputPeer(peerString);
AppMessagesManager.forwardMessages(selectedMessageIDs, inputPeer).then(function () {
selectedCancel();
});
});
}
}
function toggleEdit () { function toggleEdit () {
if ($scope.selectActions) { if ($scope.selectActions) {
selectedCancel(); selectedCancel();
@ -757,7 +795,7 @@ angular.module('myApp.controllers', [])
}; };
}) })
.controller('ChatModalController', function ($scope, $timeout, $rootScope, AppUsersManager, AppChatsManager, MtpApiManager, NotificationsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager) { .controller('ChatModalController', function ($scope, $timeout, $rootScope, AppUsersManager, AppChatsManager, MtpApiManager, NotificationsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager, ContactsSelectService) {
$scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, {}); $scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, {});
@ -826,6 +864,73 @@ angular.module('myApp.controllers', [])
}; };
$scope.inviteToGroup = function () {
var disabled = [];
angular.forEach($scope.chatFull.participants.participants, function(participant){
disabled.push(participant.user_id);
});
ContactsSelectService.selectContacts({disabled: disabled}).then(function (userIDs) {
angular.forEach(userIDs, function () {
MtpApiManager.invokeApi('messages.addChatUser', {
chat_id: $scope.chatID,
user_id: {_: 'inputUserContact', user_id: userIDs},
fwd_limit: 100
}).then(function (addResult) {
AppUsersManager.saveApiUsers(addResult.users);
AppChatsManager.saveApiChats(addResult.chats);
if (ApiUpdatesManager.saveSeq(addResult.seq)) {
ApiUpdatesManager.saveUpdate({
_: 'updateNewMessage',
message: addResult.message,
pts: addResult.pts
});
}
});
});
$rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString});
});
MtpApiManager.invokeApi('messages.addChatUser', {
chat_id: $scope.chatID,
user_id: {_: 'inputUserSelf'}
}).then(function (result) {
if (ApiUpdatesManager.saveSeq(result.seq)) {
ApiUpdatesManager.saveUpdate({
_: 'updateNewMessage',
message: result.message,
pts: result.pts
});
}
$rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString});
});
};
$scope.kickFromGroup = function (userID) {
var user = AppUsersManager.getUser(userID);
console.log({_: 'inputUserForeign', user_id: userID, access_hash: user.access_hash || '0'}, user);
MtpApiManager.invokeApi('messages.deleteChatUser', {
chat_id: $scope.chatID,
user_id: {_: 'inputUserForeign', user_id: userID, access_hash: user.access_hash || '0'}
}).then(function (result) {
if (ApiUpdatesManager.saveSeq(result.seq)) {
ApiUpdatesManager.saveUpdate({
_: 'updateNewMessage',
message: result.message,
pts: result.pts
});
}
$rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString});
});
};
$scope.flushHistory = function () { $scope.flushHistory = function () {
if (confirm('Are you sure? This can not be undone!') !== true) { if (confirm('Are you sure? This can not be undone!') !== true) {
@ -931,9 +1036,29 @@ angular.module('myApp.controllers', [])
} }
}) })
.controller('ContactsModalController', function ($scope, AppUsersManager) { .controller('ContactsModalController', function ($scope, $modalInstance, AppUsersManager) {
$scope.contacts = []; $scope.contacts = [];
$scope.search = []; $scope.search = {};
$scope.selectedContacts = {};
$scope.disabledContacts = {};
$scope.selectedCount = 0;
if ($scope.disabled) {
for (var i = 0; i < $scope.disabled.length; i++) {
$scope.disabledContacts[$scope.disabled[i]] = true;
}
}
console.log($scope.disabled, $scope.disabledContacts);
if ($scope.selected) {
for (var i = 0; i < $scope.selected.length; i++) {
if (!$scope.selectedContacts[$scope.selected[i]]) {
$scope.selectedContacts[$scope.selected[i]] = true;
$scope.selectedCount++;
}
}
}
$scope.$watch('search.query', function (newValue) { $scope.$watch('search.query', function (newValue) {
AppUsersManager.getContacts(newValue).then(function (contactsList) { AppUsersManager.getContacts(newValue).then(function (contactsList) {
@ -946,10 +1071,81 @@ angular.module('myApp.controllers', [])
} }
$scope.contacts.push(contact); $scope.contacts.push(contact);
}); });
$scope.$broadcast('contacts_change');
}); });
}) });
$scope.contactSelect = function (userID) {
if ($scope.disabledContacts[userID]) {
return false;
}
if (!$scope.multiSelect) {
return $modalInstance.close(userID);
}
if ($scope.selectedContacts[userID]) {
delete $scope.selectedContacts[userID];
$scope.selectedCount--;
} else {
$scope.selectedContacts[userID] = true;
$scope.selectedCount++;
}
};
$scope.submitSelected = function () {
if ($scope.selectedCount > 0) {
var selectedUserIDs = [];
angular.forEach($scope.selectedContacts, function (t, userID) {
selectedUserIDs.push(userID);
});
return $modalInstance.close(selectedUserIDs);
}
}
}) })
.controller('PeerSelectController', function ($scope, $modalInstance) {
$scope.dialogSelect = function (peerString) {
$modalInstance.close(peerString);
};
})
.controller('ChatCreateModalController', function ($scope, $modalInstance, $rootScope, MtpApiManager, AppChatsManager) {
$scope.group = {name: ''};
$scope.createGroup = function () {
if (!$scope.group.name) {
return;
}
var inputUsers = [];
angular.forEach($scope.userIDs, function(userID) {
inputUsers.push({_: 'inputUserContact', user_id: userID});
});
return MtpApiManager.invokeApi('messages.createChat', {
title: $scope.group.name,
users: inputUsers
}).then(function (createdResult) {
$modalInstance.close();
AppUsersManager.saveApiUsers(createdResult.users);
AppChatsManager.saveApiChats(createdResult.chats);
if (ApiUpdatesManager.saveSeq(createdResult.seq)) {
ApiUpdatesManager.saveUpdate({
_: 'updateNewMessage',
message: createdResult.message,
pts: createdResult.pts
});
}
var peerString = AppChatsManager.getChatString(message.to_id.chat_id);
$rootScope.$broadcast('history_focus', {peerString: peerString});
});
};
$scope.back = function () {
$modalInstance.dismiss();
};
})

47
app/js/directives.js

@ -81,9 +81,18 @@ angular.module('myApp.directives', ['myApp.filters'])
function updateSizes () { function updateSizes () {
if (attrs.modal) {
$(element).css({
height: $($window).height() - 200
});
updateScroller();
return;
}
$(element).css({ $(element).css({
height: $($window).height() - footer.offsetHeight - (headWrap ? headWrap.offsetHeight : 50) - 72 height: $($window).height() - footer.offsetHeight - (headWrap ? headWrap.offsetHeight : 50) - 72
}); });
updateScroller();
if (!headWrap) { if (!headWrap) {
headWrap = $('.tg_page_head')[0]; headWrap = $('.tg_page_head')[0];
} }
@ -96,6 +105,38 @@ angular.module('myApp.directives', ['myApp.filters'])
}) })
.directive('myContactsList', function($window, $timeout) {
return {
link: link
};
function link (scope, element, attrs) {
var searchWrap = $('.contacts_modal_search')[0],
panelWrap = $('.contacts_modal_panel')[0],
contactsWrap = $('.contacts_wrap', element)[0];
onContentLoaded(function () {
$(contactsWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1, iOSNativeScrolling: true});
updateSizes();
});
function updateSizes () {
$(element).css({
height: $($window).height() - (panelWrap && panelWrap.offsetHeight || 0) - (searchWrap && searchWrap.offsetHeight || 0) - 200
});
$(contactsWrap).nanoScroller();
}
$($window).on('resize', updateSizes);
scope.$on('contacts_change', function () {
onContentLoaded(updateSizes)
});
};
})
.directive('myHistory', function ($window, $timeout) { .directive('myHistory', function ($window, $timeout) {
return { return {
@ -711,7 +752,11 @@ angular.module('myApp.directives', ['myApp.filters'])
} else if (time % 1000 <= 600) { } else if (time % 1000 <= 600) {
cnt = 2; cnt = 2;
} }
element.html((new Array(cnt + 1)).join('.'));
var text = '...',
html = text.substr(0, cnt + 1) + (cnt < 2 ? ('<span class="text-invisible">' + text.substr(cnt + 1) + '</span>') : '');
element.html(html);
}, 200); }, 200);
scope.$on('$destroy', function cleanup() { scope.$on('$destroy', function cleanup() {

23
app/js/lib/mtproto.js

@ -893,12 +893,12 @@ factory('MtpDcConfigurator', function () {
var dcOptions = window._testMode var dcOptions = window._testMode
? [ ? [
{id: 1, host: '173.240.5.253', port: 80}, {id: 1, host: '173.240.5.253', port: 80},
{id: 2, host: '95.142.192.65', port: 80}, {id: 2, host: '109.239.131.195', port: 80},
{id: 3, host: '174.140.142.5', port: 80} {id: 3, host: '174.140.142.5', port: 80}
] ]
: [ : [
{id: 1, host: '173.240.5.1', port: 80}, {id: 1, host: '173.240.5.1', port: 80},
{id: 2, host: '95.142.192.66', port: 80}, {id: 2, host: '109.239.131.193', port: 80},
{id: 3, host: '174.140.142.6', port: 80}, {id: 3, host: '174.140.142.6', port: 80},
{id: 4, host: '31.210.235.12', port: 80}, {id: 4, host: '31.210.235.12', port: 80},
{id: 5, host: '116.51.22.2', port: 80}, {id: 5, host: '116.51.22.2', port: 80},
@ -1500,11 +1500,16 @@ factory('MtpSha1Service', function ($q) {
} }
}). }).
factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerator, MtpSecureRandom, MtpSha1Service, MtpAesService, AppConfigManager, $http, $q, $timeout, $interval) { factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerator, MtpSecureRandom, MtpSha1Service, MtpAesService, AppConfigManager, $http, $q, $timeout, $interval, $rootScope) {
var updatesProcessor, var updatesProcessor,
iii = 0, iii = 0,
offline = false; offline;
$rootScope.offline = true;
$rootScope.retryOnline = function () {
$(document.body).trigger('online');
}
function MtpNetworker(dcID, authKey, serverSalt, options) { function MtpNetworker(dcID, authKey, serverSalt, options) {
options = options || {}; options = options || {};
@ -1759,6 +1764,8 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato
}; };
MtpNetworker.prototype.checkConnection = function(event) { MtpNetworker.prototype.checkConnection = function(event) {
$rootScope.offlineConnecting = true;
console.log('check connection', event); console.log('check connection', event);
$timeout.cancel(this.checkConnectionPromise); $timeout.cancel(this.checkConnectionPromise);
@ -1775,21 +1782,26 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato
var self = this; var self = this;
this.sendEncryptedRequest(pingMessage).then(function (result) { this.sendEncryptedRequest(pingMessage).then(function (result) {
delete $rootScope.offlineConnecting;
self.toggleOffline(false); self.toggleOffline(false);
}, function () { }, function () {
console.log('delay ', self.checkConnectionPeriod * 1000); console.log('delay ', self.checkConnectionPeriod * 1000);
self.checkConnectionPromise = $timeout(self.checkConnection.bind(self), parseInt(self.checkConnectionPeriod * 1000)); self.checkConnectionPromise = $timeout(self.checkConnection.bind(self), parseInt(self.checkConnectionPeriod * 1000));
self.checkConnectionPeriod = Math.min(60, self.checkConnectionPeriod * 1.5); self.checkConnectionPeriod = Math.min(60, self.checkConnectionPeriod * 1.5);
$timeout(function () {
delete $rootScope.offlineConnecting;
}, 1000);
}) })
}; };
MtpNetworker.prototype.toggleOffline = function(enabled) { MtpNetworker.prototype.toggleOffline = function(enabled) {
console.log('toggle ', enabled, this.dcID, this.iii); console.log('toggle ', enabled, this.dcID, this.iii);
if (this.offline == enabled) { if (this.offline !== undefined && this.offline == enabled) {
return false; return false;
} }
this.offline = enabled; this.offline = enabled;
$rootScope.offline = enabled;
if (this.offline) { if (this.offline) {
$timeout.cancel(this.nextReqPromise); $timeout.cancel(this.nextReqPromise);
@ -1912,6 +1924,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato
var self = this; var self = this;
this.sendEncryptedRequest(message).then(function (result) { this.sendEncryptedRequest(message).then(function (result) {
self.toggleOffline(false);
self.parseResponse(result.data).then(function (response) { self.parseResponse(result.data).then(function (response) {
if (window._debugMode) { if (window._debugMode) {
console.log('Server response', self.dcID, response); console.log('Server response', self.dcID, response);

100
app/js/services.js

@ -118,7 +118,7 @@ angular.module('myApp.services', [])
}; };
}) })
.service('AppUsersManager', function ($rootScope, $modal, $modalStack, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager) { .service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager) {
var users = {}, var users = {},
contactsFillPromise, contactsFillPromise,
contactsIndex = SearchIndexManager.createIndex(); contactsIndex = SearchIndexManager.createIndex();
@ -180,12 +180,16 @@ angular.module('myApp.services', [])
return; return;
} }
if (apiUser.phone) {
apiUser.rPhone = $filter('phoneNumber')(apiUser.phone);
}
if (apiUser.first_name) { if (apiUser.first_name) {
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true}); apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true});
apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true}); apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true});
} else { } else {
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || 'DELETED'; apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || 'DELETED';
apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || 'DELETED'; apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || 'DELETED';
} }
apiUser.sortName = $.trim((apiUser.last_name || '') + ' ' + apiUser.first_name); apiUser.sortName = $.trim((apiUser.last_name || '') + ' ' + apiUser.first_name);
apiUser.sortStatus = apiUser.status && (apiUser.status.expires || apiUser.status.was_online) || 0; apiUser.sortStatus = apiUser.status && (apiUser.status.expires || apiUser.status.was_online) || 0;
@ -361,11 +365,15 @@ angular.module('myApp.services', [])
var chatFull = angular.copy(fullChat), var chatFull = angular.copy(fullChat),
chat = getChat(id); chat = getChat(id);
if (chatFull.participants && chatFull.participants._ == 'chatParticipants') { if (chatFull.participants && chatFull.participants._ == 'chatParticipants') {
angular.forEach(chatFull.participants.participants, function(participant){ MtpApiManager.getUserID().then(function (myID) {
participant.user = AppUsersManager.getUser(participant.user_id); angular.forEach(chatFull.participants.participants, function(participant){
participant.userPhoto = AppUsersManager.getUserPhoto(participant.user_id, 'User'); participant.user = AppUsersManager.getUser(participant.user_id);
participant.inviter = AppUsersManager.getUser(participant.inviter_id); participant.userPhoto = AppUsersManager.getUserPhoto(participant.user_id, 'User');
participant.inviter = AppUsersManager.getUser(participant.inviter_id);
participant.canKick = myID == chat.admin_id || myID == participant.inviter_id;
});
}); });
} }
@ -1126,6 +1134,28 @@ angular.module('myApp.services', [])
pendingByRandomID[randomIDS] = [peerID, messageID]; pendingByRandomID[randomIDS] = [peerID, messageID];
} }
function forwardMessages (msgIDs, inputPeer) {
return MtpApiManager.invokeApi('messages.forwardMessages', {
peer: inputPeer,
id: msgIDs
}).then(function (forwardResult) {
AppUsersManager.saveApiUsers(forwardResult.users);
AppChatsManager.saveApiChats(forwardResult.chats);
if (ApiUpdatesManager.saveSeq(forwardResult.seq)) {
angular.forEach(forwardResult.messages, function(apiMessage) {
ApiUpdatesManager.saveUpdate({
_: 'updateNewMessage',
message: apiMessage,
pts: forwardResult.pts
});
});
}
});
};
function finalizePendingMessage(randomID, finalMessage) { function finalizePendingMessage(randomID, finalMessage) {
var pendingData = pendingByRandomID[randomID]; var pendingData = pendingByRandomID[randomID];
@ -1178,7 +1208,7 @@ angular.module('myApp.services', [])
if (toID < 0) { if (toID < 0) {
return toID; return toID;
} else if (message.out) { } else if (message.out) {
return toID return toID;
} }
return message.from_id; return message.from_id;
} }
@ -1533,6 +1563,7 @@ angular.module('myApp.services', [])
saveMessages: saveMessages, saveMessages: saveMessages,
sendText: sendText, sendText: sendText,
sendFile: sendFile, sendFile: sendFile,
forwardMessages: forwardMessages,
getMessagePeer: getMessagePeer, getMessagePeer: getMessagePeer,
wrapForDialog: wrapForDialog, wrapForDialog: wrapForDialog,
wrapForHistory: wrapForHistory wrapForHistory: wrapForHistory
@ -2231,7 +2262,7 @@ angular.module('myApp.services', [])
'<a href="', '<a href="',
encodeEntities(match[2] + '://' + match[4]), encodeEntities(match[2] + '://' + match[4]),
'" target="_blank">', '" target="_blank">',
encodeEntities((match[2] != 'http' ? match[2] + '://' : '') + match[4]), encodeEntities(match[2] + '://' + match[4]),
'</a>' '</a>'
); );
} }
@ -2591,3 +2622,54 @@ angular.module('myApp.services', [])
showSimpleError: showSimpleError showSimpleError: showSimpleError
} }
}) })
.service('PeersSelectService', function ($rootScope, $modal) {
function selectPeer () {
var scope = $rootScope.$new();
// angular.extend(scope, params);
return $modal.open({
templateUrl: 'partials/peer_select.html',
controller: 'PeerSelectController',
scope: scope,
windowClass: 'peer_select_window'
}).result;
}
return {
selectPeer: selectPeer
}
})
.service('ContactsSelectService', function ($rootScope, $modal) {
function select (multiSelect, options) {
options = options || {};
var scope = $rootScope.$new();
scope.multiSelect = multiSelect;
angular.extend(scope, options);
return $modal.open({
templateUrl: 'partials/contacts_modal.html',
controller: 'ContactsModalController',
scope: scope,
windowClass: 'contacts_modal_window'
}).result;
}
return {
selectContacts: function (options) {
return select (true, options);
},
selectContact: function (options) {
return select (false, options);
},
}
})

30
app/partials/chat_create_modal.html

@ -0,0 +1,30 @@
<div class="contacts_modal_wrap">
<div class="modal-header">
<a class="modal-close-link" ng-click="$close()">Close</a>
<h4 class="modal-title">Create Chat</h4>
</div>
<div class="modal-body">
<form ng-submit="createGroup()">
<div class="contacts_modal_group_title">
<input class="form-control" my-focused type="text" placeholder="Group name" ng-model="group.name"/>
</div>
<div class="contacts_modal_panel clearfix">
<div class="contacts_modal_actions pull-right">
<button class="btn btn-default" ng-click="$dismiss()"> Cancel </button>
<button class="btn btn-tg" type="submit"> Create </button>
</div>
</div>
</form>
</div>
</div>

7
app/partials/chat_modal.html

@ -41,14 +41,19 @@
<div class="chat_modal_leave" ng-if="chatFull.chat.left"> <div class="chat_modal_leave" ng-if="chatFull.chat.left">
<a href="" ng-click="returnToGroup()">Return to group</a> <a href="" ng-click="returnToGroup()">Return to group</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<h5 class="chat_modal_members_header">Members</h5> <h5 class="chat_modal_members_header">
<a href="" ng-click="inviteToGroup()" ng-if="!chatFull.chat.left &amp;&amp; chatFull.participants.participants.length" class="pull-right">Add participant</a>
Members
</h5>
<div class="chat_modal_members_list"> <div class="chat_modal_members_list">
<div class="chat_modal_participant_wrap clearfix" ng-repeat="participant in chatFull.participants.participants | orderBy:'-user.sortStatus'"> <div class="chat_modal_participant_wrap clearfix" ng-repeat="participant in chatFull.participants.participants | orderBy:'-user.sortStatus'">
<a ng-if="participant.canKick" ng-click="kickFromGroup(participant.userID)" class="chat_modal_participant_kick pull-right">Kick</a>
<a ng-click="openUser(participant.user_id)" class="chat_modal_participant_photo pull-left"> <a ng-click="openUser(participant.user_id)" class="chat_modal_participant_photo pull-left">
<img <img
class="chat_modal_participant_photo" class="chat_modal_participant_photo"

53
app/partials/contacts_modal.html

@ -12,25 +12,50 @@
<a class="contacts_modal_search_clear" ng-click="search.query = ''" ng-show="search.query.length"></a> <a class="contacts_modal_search_clear" ng-click="search.query = ''" ng-show="search.query.length"></a>
</div> </div>
<div class="contacts_modal_members_list">
<div my-contacts-list class="contacts_modal_col">
<div class="contacts_modal_contact_wrap clearfix" ng-repeat="contact in contacts | orderBy:'user.sortName' track by contact.userID">
<a ng-click="openUser(contact.userID)" class="contacts_modal_contact_photo pull-left"> <div class="contacts_wrap nano">
<img <div class="contacts_scrollable_wrap content">
class="contacts_modal_contact_photo"
my-load-thumb <ul class="contacts_modal_members_list nav nav-pills nav-stacked">
thumb="contact.userPhoto"
/> <li class="contacts_modal_contact_wrap clearfix" ng-repeat="contact in contacts | orderBy:'user.sortName' track by contact.userID" ng-class="{active: selectedContacts[contact.userID], disabled: disabledContacts[contact.userID]}">
<i class="icon status_online" ng-show="contact.user.status._ == 'userStatusOnline'"></i> <a class="contacts_modal_contact" ng-click="contactSelect(contact.userID)">
</a>
<div class="contacts_modal_contact_name"> <i ng-if="multiSelect" class="icon icon-contact-tick"></i>
<a ng-click="openUser(contact.user.id)" ng-bind-html="contact.user.rFullName"></a>
<div class="contacts_modal_contact_photo pull-left">
<img
class="contacts_modal_contact_photo"
my-load-thumb
thumb="contact.userPhoto"
/>
<i class="icon status_online" ng-show="contact.user.status._ == 'userStatusOnline'"></i>
</div>
<div class="contacts_modal_contact_name" ng-bind-html="contact.user.rFullName"></div>
<div class="contacts_modal_contact_status">{{contact.user | userStatus}}</div>
</a>
</li>
</ul>
</div> </div>
<div class="contacts_modal_contact_status">{{contact.user | userStatus}}</div>
</div> </div>
</div> </div>
<div ng-if="multiSelect" class="contacts_modal_panel">
<button class="btn btn-tg btn-block" ng-class="{disabled: !selectedCount}" ng-disabled="!selectedCount" ng-click="submitSelected()" type="submit">
Next »
</button>
</div>
</div> </div>
</div> </div>

2
app/partials/dialog.html

@ -1,4 +1,4 @@
<a class="im_dialog" href="#/im?p={{dialogMessage.peerString}}"> <a class="im_dialog" ng-click="dialogSelect(dialogMessage.peerString)">
<div class="im_dialog_meta pull-right text-right"> <div class="im_dialog_meta pull-right text-right">
<div class="im_dialog_date"> <div class="im_dialog_date">

11
app/partials/head.html

@ -20,11 +20,20 @@
<div class="navbar-collapse" collapse="navbarCollapsed"> <div class="navbar-collapse" collapse="navbarCollapsed">
<ul class="nav navbar-nav navbar-right">
<ul ng-if="offline" class="nav navbar-nav navbar-offline">
<li ng-show="!offlineConnecting"><span class="navbar-offline-text">Waiting for network<span my-typing-dots></span></span></li>
<li ng-show="!offlineConnecting"><a href="" ng-click="retryOnline()">Retry</a></li>
<li ng-show="offlineConnecting"><span class="navbar-offline-text">Connecting<span my-typing-dots></span></span></li>
</ul>
<ul ng-if="!offline" class="nav navbar-nav navbar-right">
<li ng-if="isLoggedIn"><a href="" ng-click="openGroup()">New Chat</a></li>
<li ng-if="isLoggedIn"><a href="" ng-click="openContacts()">Contacts</a></li> <li ng-if="isLoggedIn"><a href="" ng-click="openContacts()">Contacts</a></li>
<li ng-if="isLoggedIn"><a href="" ng-click="openSettings()">Settings</a></li> <li ng-if="isLoggedIn"><a href="" ng-click="openSettings()">Settings</a></li>
<li><a href="https://github.com/zhukov/webogram" target="_blank">About</a></li> <li><a href="https://github.com/zhukov/webogram" target="_blank">About</a></li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>

1
app/partials/im.html

@ -79,6 +79,7 @@
<div class="im_edit_panel_wrap clearfix" ng-show="selectActions"> <div class="im_edit_panel_wrap clearfix" ng-show="selectActions">
<a class="im_edit_delete_link" ng-click="selectedDelete()"><i class="icon icon-delete"></i></a> <a class="im_edit_delete_link" ng-click="selectedDelete()"><i class="icon icon-delete"></i></a>
<a class="im_edit_forward_link" ng-click="selectedForward()">Forward</a>
<a class="im_edit_cancel_link" ng-click="selectedCancel()">Cancel</a> <a class="im_edit_cancel_link" ng-click="selectedCancel()">Cancel</a>
<h4 class="im_edit_panel_title"> <h4 class="im_edit_panel_title">
<ng-pluralize count="selectedCount" <ng-pluralize count="selectedCount"

28
app/partials/peer_select.html

@ -0,0 +1,28 @@
<div class="peer_select_modal_wrap">
<div class="modal-header">
<a class="modal-close-link" ng-click="$close()">Close</a>
<h4 class="modal-title">Select conversation</h4>
</div>
<div class="modal-body">
<div class="im_dialogs_modal_col_wrap" ng-controller="AppImDialogsController">
<div class="im_dialogs_search">
<input class="form-control im_dialogs_search_field" type="search" placeholder="Search" ng-model="search.query"/>
<a class="im_dialogs_search_clear" ng-click="search.query = ''" ng-show="search.query.length"></a>
</div>
<div my-dialogs-list modal="true" class="im_dialogs_modal_col">
<div class="im_dialogs_wrap nano">
<div class="im_dialogs_scrollable_wrap content">
<ul class="nav nav-pills nav-stacked">
<li class="im_dialog_wrap" my-dialog dialog-message="dialogMessage" ng-repeat="dialogMessage in dialogs track by dialogMessage.peerID"></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
Loading…
Cancel
Save