Browse Source

Added dialogs search, improved emoji

master
Igor Zhukov 10 years ago
parent
commit
5c6a185d11
  1. 2
      app/css/app.css
  2. 10
      app/index.html
  3. 2
      app/js/app.js
  4. 58
      app/js/controllers.js
  5. 178
      app/js/services.js
  6. 6
      app/partials/im.html
  7. 2
      app/partials/photo_modal.html
  8. 2
      app/partials/video_modal.html
  9. 21
      app/vendor/jquery.emojiarea/jquery.emojiarea.js

2
app/css/app.css

@ -1314,7 +1314,7 @@ img.img_fullsize { @@ -1314,7 +1314,7 @@ img.img_fullsize {
position: absolute;
z-index: 999;
width: 180px;
margin-left: -90px;
margin-left: -91px;
margin-top: -232px;
overflow: hidden;
}

10
app/index.html

@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
<link rel="stylesheet" href="vendor/angular/angular-csp.css"/>
<link rel="stylesheet" href="vendor/bootstrap/css/bootstrap.css"/>
<link rel="stylesheet" href="vendor/jquery.nanoscroller/nanoscroller.css"/>
<link rel="stylesheet" href="css/app.css?18"/>
<link rel="stylesheet" href="css/app.css?19"/>
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<meta property="og:title" content="Webogram">
@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
<script type="text/javascript" src="vendor/console-polyfill/console-polyfill.js?1"></script>
<script type="text/javascript" src="vendor/jquery/jquery.min.js"></script>
<script type="text/javascript" src="vendor/jquery.nanoscroller/nanoscroller.js"></script>
<script type="text/javascript" src="vendor/jquery.emojiarea/jquery.emojiarea.js?4"></script>
<script type="text/javascript" src="vendor/jquery.emojiarea/jquery.emojiarea.js?5"></script>
<script type="text/javascript" src="vendor/angular/angular.js?1"></script>
<script type="text/javascript" src="vendor/angular/angular-route.js?1"></script>
@ -51,9 +51,9 @@ @@ -51,9 +51,9 @@
<script type="text/javascript" src="js/lib/mtproto.js?17"></script>
<script type="text/javascript" src="js/util.js"></script>
<script type="text/javascript" src="js/app.js?13"></script>
<script type="text/javascript" src="js/services.js?14"></script>
<script type="text/javascript" src="js/controllers.js?22"></script>
<script type="text/javascript" src="js/app.js?14"></script>
<script type="text/javascript" src="js/services.js?16"></script>
<script type="text/javascript" src="js/controllers.js?23"></script>
<script type="text/javascript" src="js/filters.js?3"></script>
<script type="text/javascript" src="js/directives.js?15"></script>

2
app/js/app.js

@ -57,7 +57,7 @@ config(['$locationProvider', '$routeProvider', '$compileProvider', function($loc @@ -57,7 +57,7 @@ config(['$locationProvider', '$routeProvider', '$compileProvider', function($loc
// $locationProvider.html5Mode(true);
$routeProvider.when('/', {templateUrl: 'partials/welcome.html?3', controller: 'AppWelcomeController'});
$routeProvider.when('/login', {templateUrl: 'partials/login.html?4', controller: 'AppLoginController'});
$routeProvider.when('/im', {templateUrl: 'partials/im.html?10', controller: 'AppIMController', reloadOnSearch: false});
$routeProvider.when('/im', {templateUrl: 'partials/im.html?11', controller: 'AppIMController', reloadOnSearch: false});
$routeProvider.otherwise({redirectTo: '/'});
}]);

58
app/js/controllers.js

@ -145,11 +145,12 @@ angular.module('myApp.controllers', []) @@ -145,11 +145,12 @@ angular.module('myApp.controllers', [])
};
})
.controller('AppIMController', function ($scope, $location, $routeParams, $modal, $rootScope, MtpApiManager) {
.controller('AppIMController', function ($scope, $location, $routeParams, $modal, $rootScope, $modalStack, MtpApiManager) {
$scope.$on('$routeUpdate', updateCurDialog);
$scope.$on('history_focus', function (e, peerData) {
$modalStack.dismissAll();
if (peerData.peerString == $scope.curDialog.peer) {
$scope.$broadcast('ui_history_focus');
} else {
@ -182,12 +183,13 @@ angular.module('myApp.controllers', []) @@ -182,12 +183,13 @@ angular.module('myApp.controllers', [])
// console.log('init controller');
$scope.dialogs = [];
$scope.search = {};
var offset = 0,
maxID = 0,
hasMore = false,
limit = 20;
startLimit = 20,
limit = 100;
MtpApiManager.invokeApi('account.updateStatus', {offline: false});
$scope.$on('dialogs_need_more', function () {
@ -204,6 +206,10 @@ angular.module('myApp.controllers', []) @@ -204,6 +206,10 @@ angular.module('myApp.controllers', [])
});
$scope.$on('dialogs_update', function (e, dialog) {
if ($scope.search.query !== undefined && $scope.search.query.length) {
return false;
}
var pos = false;
angular.forEach($scope.dialogs, function(curDialog, curPos) {
if (curDialog.peerID == dialog.peerID) {
@ -220,27 +226,33 @@ angular.module('myApp.controllers', []) @@ -220,27 +226,33 @@ angular.module('myApp.controllers', [])
$scope.dialogs.unshift(wrappedDialog);
});
loadDialogs();
$scope.$watch('search.query', loadDialogs);
function loadDialogs (startLimit) {
function loadDialogs () {
offset = 0;
maxID = 0;
hasMore = false;
startLimit = startLimit || limit;
AppMessagesManager.getDialogs(maxID, startLimit).then(function (dialogsResult) {
offset += startLimit;
maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message;
hasMore = offset < dialogsResult.count;
AppMessagesManager.getDialogs($scope.search.query, maxID, startLimit).then(function (dialogsResult) {
$scope.dialogs = [];
angular.forEach(dialogsResult.dialogs, function (dialog) {
$scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count));
});
if (dialogsResult.dialogs.length) {
offset += startLimit;
maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message;
hasMore = offset < dialogsResult.count;
angular.forEach(dialogsResult.dialogs, function (dialog) {
$scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count));
});
}
$scope.$broadcast('ui_dialogs_change');
if (!$scope.search.query) {
AppMessagesManager.getDialogs('', maxID, limit);
}
}, function (error) {
if (error.code == 401) {
$location.url('/login');
@ -253,7 +265,7 @@ angular.module('myApp.controllers', []) @@ -253,7 +265,7 @@ angular.module('myApp.controllers', [])
return;
}
AppMessagesManager.getDialogs(maxID, limit).then(function (dialogsResult) {
AppMessagesManager.getDialogs($scope.search.query, maxID, limit).then(function (dialogsResult) {
offset += limit;
maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message;
hasMore = offset < dialogsResult.count;
@ -280,7 +292,12 @@ angular.module('myApp.controllers', []) @@ -280,7 +292,12 @@ angular.module('myApp.controllers', [])
$scope.history = [];
$scope.typing = {};
var peerID, offset, hasMore, maxID, limit = 20;
var peerID,
offset = 0,
hasMore = false,
maxID = 0,
startLimit = 20,
limit = 50;
function applyDialogSelect (newPeer) {
newPeer = newPeer || $scope.curDialog.peer || '';
@ -351,8 +368,8 @@ angular.module('myApp.controllers', []) @@ -351,8 +368,8 @@ angular.module('myApp.controllers', [])
offset = 0;
maxID = 0;
AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, limit).then(function (historyResult) {
offset += limit;
AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, startLimit).then(function (historyResult) {
offset += startLimit;
hasMore = offset < historyResult.count;
maxID = historyResult.history[historyResult.history.length - 1];
@ -553,7 +570,6 @@ angular.module('myApp.controllers', []) @@ -553,7 +570,6 @@ angular.module('myApp.controllers', [])
.controller('UserModalController', function ($scope, $location, $rootScope, $modalStack, AppUsersManager) {
$scope.user = AppUsersManager.wrapForFull($scope.userID);
$scope.goToHistory = function () {
$modalStack.dismissAll();
$rootScope.$broadcast('history_focus', {peerString: $scope.user.peerString});
};
})

178
app/js/services.js

@ -376,6 +376,17 @@ angular.module('myApp.services', []) @@ -376,6 +376,17 @@ angular.module('myApp.services', [])
};
}
},
getPeerSearchText: function (peerID) {
var text;
if (peerID > 0) {
var user = AppUsersManager.getUser(peerID);
text = (user.first_name || '') + ' ' + (user.last_name || '') + ' ' + (user.phone || '');
} else if (peerID < 0) {
var chat = AppChatsManager.getChat(-peerID);
text = chat.title || '';
}
return text;
},
getOutputPeer: function (peerID) {
return peerID > 0
? {_: 'peerUser', user_id: peerID}
@ -405,7 +416,94 @@ angular.module('myApp.services', []) @@ -405,7 +416,94 @@ angular.module('myApp.services', [])
}
})
.service('AppMessagesManager', function ($q, $rootScope, $filter, $sanitize, $location, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager) {
.service('SearchIndexManager', function () {
var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g,
trimRe = /^\s+|\s$/g;
return {
createIndex: createIndex,
indexObject: indexObject,
search: search
};
function createIndex () {
return {
shortIndexes: {},
fullTexts: {}
}
}
function indexObject (id, searchText, searchIndex) {
if (searchIndex.fullTexts[id] !== undefined) {
return false;
}
searchText = searchText.replace(badCharsRe, ' ').replace(trimRe, '').toLowerCase();
if (!searchText.length) {
return false;
}
var shortIndexes = searchIndex.shortIndexes;
searchIndex.fullTexts[id] = searchText;
angular.forEach(searchText.split(' '), function(searchWord) {
var len = Math.min(searchWord.length, 3),
wordPart, i;
for (i = 1; i <= len; i++) {
wordPart = searchWord.substr(0, i);
if (shortIndexes[wordPart] === undefined) {
shortIndexes[wordPart] = [id];
} else {
shortIndexes[wordPart].push(id);
}
}
});
}
function search (query, searchIndex) {
var shortIndexes = searchIndex.shortIndexes,
fullTexts = searchIndex.fullTexts;
query = query.replace(badCharsRe, ' ').replace(trimRe, '').toLowerCase();
var queryWords = query.split(' '),
foundObjs = false,
newFoundObjs, i, j, searchText, found;
for (i = 0; i < queryWords.length; i++) {
newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)];
if (!newFoundObjs) {
foundObjs = [];
break;
}
if (foundObjs === false || foundObjs.length > newFoundObjs.length) {
foundObjs = newFoundObjs;
}
}
newFoundObjs = {};
for (j = 0; j < foundObjs.length; j++) {
found = true;
searchText = fullTexts[foundObjs[j]];
for (i = 0; i < queryWords.length; i++) {
if (searchText.indexOf(queryWords[i]) == -1) {
found = false;
break;
}
}
if (found) {
newFoundObjs[foundObjs[j]] = true;
}
}
return newFoundObjs;
}
})
.service('AppMessagesManager', function ($q, $rootScope, $filter, $sanitize, $location, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, SearchIndexManager) {
var messagesStorage = {};
var messagesForHistory = {};
@ -415,25 +513,51 @@ angular.module('myApp.services', []) @@ -415,25 +513,51 @@ angular.module('myApp.services', [])
var pendingByMessageID = {};
var tempID = -1;
var dialogsIndex = SearchIndexManager.createIndex(),
cachedResults = {query: false};
NotificationsManager.start();
function getDialogs (maxID, limit) {
function getDialogs (query, maxID, limit) {
var curDialogStorage = dialogsStorage;
if (angular.isString(query) && query.length) {
if (!limit || cachedResults.query !== query) {
cachedResults.query = query;
var results = SearchIndexManager.search(query, dialogsIndex);
cachedResults.dialogs = [];
angular.forEach(dialogsStorage.dialogs, function (dialog) {
if (results[dialog.peerID]) {
cachedResults.dialogs.push(dialog);
}
});
cachedResults.count = cachedResults.dialogs.length;
}
curDialogStorage = cachedResults;
} else {
cachedResults.query = false;
}
var offset = 0;
if (maxID > 0) {
for (offset = 0; offset < dialogsStorage.dialogs.length; offset++) {
if (maxID > dialogsStorage.dialogs[offset].top_message) {
for (offset = 0; offset < curDialogStorage.dialogs.length; offset++) {
if (maxID > curDialogStorage.dialogs[offset].top_message) {
break;
}
}
}
if (dialogsStorage.count !== null && (
dialogsStorage.dialogs.length >= offset + limit ||
dialogsStorage.dialogs.length == dialogsStorage.count
if (curDialogStorage.count !== null && (
curDialogStorage.dialogs.length >= offset + limit ||
curDialogStorage.dialogs.length == curDialogStorage.count
)) {
return $q.when({
count: dialogsStorage.count,
dialogs: dialogsStorage.dialogs.slice(offset, offset + limit)
count: curDialogStorage.count,
dialogs: curDialogStorage.dialogs.slice(offset, offset + limit)
});
}
@ -449,29 +573,34 @@ angular.module('myApp.services', []) @@ -449,29 +573,34 @@ angular.module('myApp.services', [])
saveMessages(dialogsResult.messages);
if (maxID > 0) {
for (offset = 0; offset < dialogsStorage.dialogs.length; offset++) {
if (maxID > dialogsStorage.dialogs[offset].top_message) {
for (offset = 0; offset < curDialogStorage.dialogs.length; offset++) {
if (maxID > curDialogStorage.dialogs[offset].top_message) {
break;
}
}
}
dialogsStorage.count = dialogsResult._ == 'messages.dialogsSlice'
curDialogStorage.count = dialogsResult._ == 'messages.dialogsSlice'
? dialogsResult.count
: dialogsResult.dialogs.length;
dialogsStorage.dialogs.splice(offset, dialogsStorage.dialogs.length - offset);
curDialogStorage.dialogs.splice(offset, curDialogStorage.dialogs.length - offset);
angular.forEach(dialogsResult.dialogs, function (dialog) {
dialogsStorage.dialogs.push({
peerID: AppPeersManager.getPeerID(dialog.peer),
var peerID = AppPeersManager.getPeerID(dialog.peer),
peerText = AppPeersManager.getPeerSearchText(peerID);
SearchIndexManager.indexObject(peerID, peerText, dialogsIndex);
curDialogStorage.dialogs.push({
peerID: peerID,
top_message: dialog.top_message,
unread_count: dialog.unread_count
});
});
deferred.resolve({
count: dialogsStorage.count,
dialogs: dialogsStorage.dialogs.slice(offset, offset + limit)
count: curDialogStorage.count,
dialogs: curDialogStorage.dialogs.slice(offset, offset + limit)
});
}, function (error) {
deferred.reject(error);
@ -1104,6 +1233,9 @@ angular.module('myApp.services', []) @@ -1104,6 +1233,9 @@ angular.module('myApp.services', [])
dialog.unread_count++;
}
dialog.top_message = message.id;
SearchIndexManager.indexObject(peerID, AppPeersManager.getPeerSearchText(peerID), dialogsIndex);
dialogsStorage.dialogs.unshift(dialog);
$rootScope.$broadcast('dialogs_update', dialog);
@ -1193,7 +1325,7 @@ angular.module('myApp.services', []) @@ -1193,7 +1325,7 @@ angular.module('myApp.services', [])
}
});
console.log('choosing', photo, width, height, bestPhotoSize);
// console.log('choosing', photo, width, height, bestPhotoSize);
return bestPhotoSize;
}
@ -1266,7 +1398,7 @@ angular.module('myApp.services', []) @@ -1266,7 +1398,7 @@ angular.module('myApp.services', [])
scope.photoID = photoID;
var modalInstance = $modal.open({
templateUrl: 'partials/photo_modal.html',
templateUrl: 'partials/photo_modal.html?1',
controller: 'PhotoModalController',
scope: scope,
backdrop: 'static'
@ -1360,12 +1492,9 @@ angular.module('myApp.services', []) @@ -1360,12 +1492,9 @@ angular.module('myApp.services', [])
scope.videoID = videoID;
scope.progress = {enabled: false};
scope.player = {};
// scope.close = function () {
// modalInstance.close();
// }
var modalInstance = $modal.open({
templateUrl: 'partials/video_modal.html',
templateUrl: 'partials/video_modal.html?1',
controller: 'VideoModalController',
scope: scope
});
@ -1740,9 +1869,6 @@ angular.module('myApp.services', []) @@ -1740,9 +1869,6 @@ angular.module('myApp.services', [])
var regExp = new RegExp('((?:(ftp|https?)://|(?:mailto:)?([A-Za-z0-9._%+-]+@))(\\S*\\.\\S*[^\\s.;,(){}<>"\']))|(\\n)|(' + emojiUtf.join('|') + ')', 'i');
// console.log(regExp);
return {
wrapRichText: wrapRichText
};

6
app/partials/im.html

@ -6,14 +6,14 @@ @@ -6,14 +6,14 @@
<div class="im_dialogs_col_wrap" ng-controller="AppImDialogsController">
<div class="im_dialogs_search">
<input class="form-control im_dialogs_search_field" type="search" placeholder="Search" ng-model="searchQuery"/>
<a class="im_dialogs_search_clear" ng-click="searchQuery = ''" ng-show="searchQuery.length"></a>
<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 class="im_dialogs_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 | filter:searchQuery" ng-class="{active: curDialog.peerID == dialogMessage.peerID}"></li>
<li class="im_dialog_wrap" my-dialog dialog-message="dialogMessage" ng-repeat="dialogMessage in dialogs" ng-class="{active: curDialog.peerID == dialogMessage.peerID}"></li>
</ul>
</div>
</div>

2
app/partials/photo_modal.html

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
<div class="photo_modal_image_wrap" my-load-full-photo full-photo="photo.full" thumb-location="photo.thumb.location" ng-click="$close()"> </div>
<p class="media_modal_info">From: <span class="media_modal_author">{{photo.fromUser | userName}}</span>, {{photo.date | dateOrTime}}</p>
<p class="media_modal_info">From: <span class="media_modal_author" ng-bind-html="photo.fromUser.rFullName" ></span>, {{photo.date | dateOrTime}}</p>
</div>

2
app/partials/video_modal.html

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
<div class="video_modal_image_wrap" my-load-video video="video"></div>
<p class="media_modal_info">From: <span class="media_modal_author">{{video.fromUser | userName}}</span>, {{video.date | dateOrTime}}</p>
<p class="media_modal_info">From: <span class="media_modal_author" ng-bind-html="video.fromUser.rFullName"></span>, {{video.date | dateOrTime}}</p>
</div>

21
app/vendor/jquery.emojiarea/jquery.emojiarea.js vendored

@ -321,6 +321,16 @@ @@ -321,6 +321,16 @@
util.restoreSelection(this.selection);
}
try { util.replaceSelection($img[0]); } catch (e) {}
/*! MODIFICATION START
Following code was modified by Igor Zhukov, in order to improve selection handling
*/
var self = this;
setTimeout(function () {
self.selection = util.saveSelection();
}, 100);
/*! MODIFICATION END */
this.onChange();
};
@ -418,7 +428,7 @@ @@ -418,7 +428,7 @@
var target = e.originalTarget || e.target || window;
while (target && target != window) {
target = target.parentNode;
if (target == self.$menu[0]) {
if (target == self.$menu[0] || self.emojiarea && target == self.emojiarea.$button[0]) {
return;
}
}
@ -438,11 +448,11 @@ @@ -438,11 +448,11 @@
this.$menu.on('click', 'a', function(e) {
var emoji = $('.label', $(this)).text();
window.setTimeout(function() {
self.onItemSelected(emoji);
/*! MODIFICATION START
Following code was modified by Igor Zhukov, in order to prevent close on shift-, ctrl-, alt- emoji select
Following code was modified by Igor Zhukov, in order to close only on ctrl-, alt- emoji select
*/
self.onItemSelected(emoji);
if (!e.shiftKey && !e.ctrlKey && !e.metaKey) {
if (e.ctrlKey || e.metaKey) {
self.hide();
}
/*! MODIFICATION END */
@ -507,7 +517,8 @@ @@ -507,7 +517,8 @@
};
EmojiMenu.prototype.show = function(emojiarea) {
if (this.emojiarea && this.emojiarea === emojiarea) return;
/* MODIFICATION: Following line was modified by Igor Zhukov, in order to improve EmojiMenu behaviour */
if (this.emojiarea && this.emojiarea === emojiarea) return this.hide();
emojiarea.$button.addClass('on');
this.emojiarea = emojiarea;
this.emojiarea.menu = this;

Loading…
Cancel
Save