From 5c6a185d1166986472f6f655d2ecaef7986023af Mon Sep 17 00:00:00 2001 From: Igor Zhukov Date: Tue, 4 Feb 2014 23:22:53 +0400 Subject: [PATCH] Added dialogs search, improved emoji --- app/css/app.css | 2 +- app/index.html | 10 +- app/js/app.js | 2 +- app/js/controllers.js | 58 +++--- app/js/services.js | 178 +++++++++++++++--- app/partials/im.html | 6 +- app/partials/photo_modal.html | 2 +- app/partials/video_modal.html | 2 +- .../jquery.emojiarea/jquery.emojiarea.js | 21 ++- 9 files changed, 217 insertions(+), 64 deletions(-) diff --git a/app/css/app.css b/app/css/app.css index 4a066aa7..ee6ae73d 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -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; } diff --git a/app/index.html b/app/index.html index 7746eb45..bbe74ad7 100644 --- a/app/index.html +++ b/app/index.html @@ -7,7 +7,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -51,9 +51,9 @@ - - - + + + diff --git a/app/js/app.js b/app/js/app.js index 6e0acc8a..c3f8ddb5 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -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: '/'}); }]); diff --git a/app/js/controllers.js b/app/js/controllers.js index 89fa6b36..b5285d2c 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -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', []) // 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', []) }); $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', []) $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', []) 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', []) $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', []) 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', []) .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}); }; }) diff --git a/app/js/services.js b/app/js/services.js index 57da48e0..96f6f933 100644 --- a/app/js/services.js +++ b/app/js/services.js @@ -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', []) } }) -.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', []) 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', []) 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', []) 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', []) } }); - console.log('choosing', photo, width, height, bestPhotoSize); + // console.log('choosing', photo, width, height, bestPhotoSize); return bestPhotoSize; } @@ -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', []) 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', []) var regExp = new RegExp('((?:(ftp|https?)://|(?:mailto:)?([A-Za-z0-9._%+-]+@))(\\S*\\.\\S*[^\\s.;,(){}<>"\']))|(\\n)|(' + emojiUtf.join('|') + ')', 'i'); - // console.log(regExp); - - return { wrapRichText: wrapRichText }; diff --git a/app/partials/im.html b/app/partials/im.html index 4698ab46..8d7fb21a 100644 --- a/app/partials/im.html +++ b/app/partials/im.html @@ -6,14 +6,14 @@
diff --git a/app/partials/photo_modal.html b/app/partials/photo_modal.html index 02876897..d6090911 100644 --- a/app/partials/photo_modal.html +++ b/app/partials/photo_modal.html @@ -4,7 +4,7 @@
-

From: {{photo.fromUser | userName}}, {{photo.date | dateOrTime}}

+

From: , {{photo.date | dateOrTime}}

diff --git a/app/partials/video_modal.html b/app/partials/video_modal.html index 3916660c..b47d9bab 100644 --- a/app/partials/video_modal.html +++ b/app/partials/video_modal.html @@ -4,7 +4,7 @@
-

From: {{video.fromUser | userName}}, {{video.date | dateOrTime}}

+

From: , {{video.date | dateOrTime}}

diff --git a/app/vendor/jquery.emojiarea/jquery.emojiarea.js b/app/vendor/jquery.emojiarea/jquery.emojiarea.js index b932594c..0a24f9bf 100644 --- a/app/vendor/jquery.emojiarea/jquery.emojiarea.js +++ b/app/vendor/jquery.emojiarea/jquery.emojiarea.js @@ -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 @@ 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 @@ 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 @@ }; 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;