Example #1
0
    var MailListCtrl = function($scope, $routeParams) {
        //
        // Init
        //

        emailDao = appController._emailDao;
        outboxBo = appController._outboxBo;
        keychainDao = appController._keychain;

        //
        // scope functions
        //

        $scope.getBody = function(email) {
            emailDao.getBody({
                folder: currentFolder(),
                message: email
            }, function(err) {
                if (err && err.code !== 42) {
                    $scope.onError(err);
                    return;
                }

                // display fetched body
                $scope.$digest();

                // automatically decrypt if it's the selected email
                if (email === currentMessage()) {
                    emailDao.decryptBody({
                        message: email
                    }, $scope.onError);
                }
            });
        };

        /**
         * Called when clicking on an email list item
         */
        $scope.select = function(email) {
            // unselect an item
            if (!email) {
                $scope.state.mailList.selected = undefined;
                return;
            }

            $scope.state.mailList.selected = email;

            if (!firstSelect) {
                // only toggle to read view on 2nd select in mobile mode
                $scope.state.read.toggle(true);
            }
            firstSelect = false;

            keychainDao.refreshKeyForUserId(email.from[0].address, onKeyRefreshed);

            function onKeyRefreshed(err) {
                if (err) {
                    $scope.onError(err);
                }

                emailDao.decryptBody({
                    message: email
                }, $scope.onError);

                // if the email is unread, please sync the new state.
                // otherweise forget about it.
                if (!email.unread) {
                    return;
                }

                $scope.toggleUnread(email);
            }
        };

        /**
         * Mark an email as unread or read, respectively
         */
        $scope.toggleUnread = function(message) {
            updateStatus('Updating unread flag...');

            message.unread = !message.unread;
            emailDao.setFlags({
                folder: currentFolder(),
                message: message
            }, function(err) {
                if (err && err.code === 42) {
                    // offline, restore
                    message.unread = !message.unread;
                    updateStatus('Unable to mark unread flag in offline mode!');
                    return;
                }

                if (err) {
                    updateStatus('Error on sync!');
                    $scope.onError(err);
                    return;
                }

                updateStatus('Online');
                $scope.$apply();
            });
        };

        /**
         * Delete a message
         */
        $scope.remove = function(message) {
            if (!message) {
                return;
            }

            updateStatus('Deleting message...');
            remove();

            function remove() {
                emailDao.deleteMessage({
                    folder: currentFolder(),
                    message: message
                }, function(err) {
                    if (err) {
                        // show errors where appropriate
                        if (err.code === 42) {
                            $scope.select(message);
                            updateStatus('Unable to delete message in offline mode!');
                            return;
                        }
                        updateStatus('Error during delete!');
                        $scope.onError(err);
                    }
                    updateStatus('Message deleted!');
                    $scope.$apply();
                });
            }
        };

        // share local scope functions with root state
        $scope.state.mailList = {
            remove: $scope.remove
        };

        //
        // watch tasks
        //

        /**
         * List emails from folder when user changes folder
         */
        $scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() {
            if (!currentFolder()) {
                return;
            }

            // reset searchFilter
            $scope.searchText = undefined;

            // in development, display dummy mail objects
            if ($routeParams.dev) {
                updateStatus('Last update: ', new Date());
                currentFolder().messages = createDummyMails();
                return;
            }

            // display and select first
            openCurrentFolder();
        });

        $scope.watchMessages = $scope.$watchCollection('state.nav.currentFolder.messages', function(messages) {
            if (!messages) {
                return;
            }

            // sort message by uid
            currentFolder().messages.sort(byUidDescending);
            // set display buffer to first messages
            $scope.displayMessages = currentFolder().messages.slice(0, INIT_DISPLAY_LEN);

            // Shows the next message based on the uid of the currently selected element
            if (currentFolder().messages.indexOf(currentMessage()) === -1) {
                firstSelect = true; // reset first selection
                $scope.select($scope.displayMessages[0]);
            }
        });

        /**
         * display more items (for infinite scrolling)
         */
        $scope.displayMore = function() {
            if (!currentFolder() || !$scope.displayMessages) {
                // folders not yet initialized
                return;
            }

            var len = currentFolder().messages.length,
                dLen = $scope.displayMessages.length;

            if (dLen === len || $scope.searchText) {
                // all messages are already displayed or we're in search mode
                return;
            }

            // copy next interval of messages to the end of the display messages array
            var next = currentFolder().messages.slice(dLen, dLen + SCROLL_DISPLAY_LEN);
            Array.prototype.push.apply($scope.displayMessages, next);
        };

        /**
         * This method is called when the user changes the searchText
         */
        $scope.displaySearchResults = function(searchText) {
            if (searchTimeout) {
                // remove timeout to wait for user typing query
                clearTimeout(searchTimeout);
            }

            if (!searchText) {
                // set display buffer to first messages
                $scope.displayMessages = currentFolder().messages.slice(0, INIT_DISPLAY_LEN);
                $scope.searching = false;
                updateStatus('Online');
                return;
            }

            // display searching spinner
            $scope.searching = true;
            updateStatus('Searching ...');
            searchTimeout = setTimeout(function() {
                $scope.$apply(function() {
                    // filter relevant messages
                    $scope.displayMessages = $scope.search(currentFolder().messages, searchText);
                    $scope.searching = false;
                    updateStatus('Matches in this folder');
                });
            }, 500);
        };

        /**
         * Do full text search on messages. Parse meta data first
         */
        $scope.search = function(messages, searchText) {
            // don't filter on empty searchText
            if (!searchText) {
                return messages;
            }

            // escape search string
            searchText = searchText.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
            // compare all strings (case insensitive)
            var regex = new RegExp(searchText, 'i');

            function contains(input) {
                if (!input) {
                    return false;
                }
                return regex.test(input);
            }

            function checkAddresses(header) {
                if (!header || !header.length) {
                    return false;
                }

                for (var i = 0; i < header.length; i++) {
                    if (contains(header[i].name) || contains(header[i].address)) {
                        return true;
                    }
                }

                return false;
            }

            /**
             * Filter meta data first and then only look at plaintext and decrypted message bodies
             */
            function matchMetaDataFirst(m) {
                // compare subject
                if (contains(m.subject)) {
                    return true;
                }
                // compares address headers
                if (checkAddresses(m.from) || checkAddresses(m.to) || checkAddresses(m.cc) || checkAddresses(m.bcc)) {
                    return true;
                }
                // compare plaintext body
                if (m.body && !m.encrypted && contains(m.body)) {
                    return true;
                }
                // compare decrypted body
                if (m.body && m.encrypted && m.decrypted && contains(m.body)) {
                    return true;
                }
                // compare plaintex html body
                if (m.html && !m.encrypted && contains(m.html)) {
                    return true;
                }
                // compare decrypted html body
                if (m.html && m.encrypted && m.decrypted && contains(m.html)) {
                    return true;
                }
                return false;
            }

            // user native js Array.filter
            return messages.filter(matchMetaDataFirst);
        };

        /**
         * Sync current folder when client comes back online
         */
        $scope.watchOnline = $scope.$watch('account.online', function(isOnline) {
            if (isOnline) {
                updateStatus('Online');
                openCurrentFolder();
            } else {
                updateStatus('Offline mode');
            }
        }, true);

        //
        // Helper Functions
        //

        function openCurrentFolder() {
            emailDao.openFolder({
                folder: currentFolder()
            }, function(error) {
                // dont wait until scroll to load visible mail bodies
                $scope.loadVisibleBodies();

                // don't display error for offline case
                if (error && error.code === 42) {
                    return;
                }
                $scope.onError(error);
            });
        }

        function updateStatus(lbl, time) {
            $scope.lastUpdateLbl = lbl;
            $scope.lastUpdate = (time) ? time : '';
        }

        function currentFolder() {
            return $scope.state.nav.currentFolder;
        }

        function currentMessage() {
            return $scope.state.mailList.selected;
        }

        //
        // Notification API
        //

        (emailDao || {}).onIncomingMessage = function(msgs) {
            var popupId, popupTitle, popupMessage, unreadMsgs;

            unreadMsgs = msgs.filter(function(msg) {
                return msg.unread;
            });

            if (unreadMsgs.length === 0) {
                return;
            }

            popupId = '' + unreadMsgs[0].uid;
            if (unreadMsgs.length > 1) {
                popupTitle = unreadMsgs.length + ' new messages';
                popupMessage = _.pluck(unreadMsgs, 'subject').join('\n');
            } else {
                popupTitle = unreadMsgs[0].from[0].name || unreadMsgs[0].from[0].address;
                popupMessage = unreadMsgs[0].subject;
            }

            notification.create({
                id: popupId,
                title: popupTitle,
                message: popupMessage
            });
        };

        notification.setOnClickedListener(function(uidString) {
            var uid = parseInt(uidString, 10);

            if (isNaN(uid)) {
                return;
            }

            firstSelect = false;
            $scope.select(_.findWhere(currentFolder().messages, {
                uid: uid
            }));
        });
    };
Example #2
0
    var MailListCtrl = function($scope, $timeout) {
        //
        // Init
        //

        emailDao = appController._emailDao;
        outboxBo = appController._outboxBo;
        emailSync = appController._emailSync;

        emailDao.onNeedsSync = function(error, folder) {
            if (error) {
                $scope.onError(error);
                return;
            }

            $scope.synchronize({
                folder: folder
            });
        };

        emailSync.onIncomingMessage = function(msgs) {
            var popupId, popupTitle, popupMessage, unreadMsgs;

            unreadMsgs = msgs.filter(function(msg) {
                return msg.unread;
            });

            if (unreadMsgs.length === 0) {
                return;
            }

            popupId = '' + unreadMsgs[0].uid;
            if (unreadMsgs.length > 1) {
                popupTitle = unreadMsgs.length + ' new messages';
                popupMessage = _.pluck(unreadMsgs, 'subject').join('\n');
            } else {
                popupTitle = unreadMsgs[0].from[0].name || unreadMsgs[0].from[0].address;
                popupMessage = unreadMsgs[0].subject;
            }

            notification.create({
                id: popupId,
                title: popupTitle,
                message: popupMessage
            });
        };

        notification.setOnClickedListener(function(uidString) {
            var uid = parseInt(uidString, 10);

            if (isNaN(uid)) {
                return;
            }

            $scope.select(_.findWhere(currentFolder().messages, {
                uid: uid
            }));
        });


        //
        // scope functions
        //

        $scope.getBody = function(email) {
            emailDao.getBody({
                folder: currentFolder().path,
                message: email
            }, function(err) {
                if (err && err.code !== 42) {
                    $scope.onError(err);
                    return;
                }

                // display fetched body
                $scope.$digest();

                // automatically decrypt if it's the selected email
                if (email === $scope.state.mailList.selected) {
                    emailDao.decryptBody({
                        message: email
                    }, $scope.onError);
                }
            });
        };

        /**
         * Called when clicking on an email list item
         */
        $scope.select = function(email) {
            // unselect an item
            if (!email) {
                $scope.state.mailList.selected = undefined;
                return;
            }

            $scope.state.mailList.selected = email;
            $scope.state.read.toggle(true);

            emailDao.decryptBody({
                message: email
            }, $scope.onError);

            // if the email is unread, please sync the new state.
            // otherweise forget about it.
            if (!email.unread) {
                return;
            }

            email.unread = false;
            $scope.synchronize();
        };

        /**
         * Mark an email as unread or read, respectively
         */
        $scope.toggleUnread = function(email) {
            email.unread = !email.unread;
            $scope.synchronize();
        };

        /**
         * Synchronize the selected imap folder to local storage
         */
        $scope.synchronize = function(options) {
            updateStatus('Syncing ...');

            options = options || {};
            options.folder = options.folder || currentFolder().path;

            // let email dao handle sync transparently
            if (currentFolder().type === 'Outbox') {
                emailDao.syncOutbox({
                    folder: currentFolder().path
                }, done);
            } else {
                emailDao.sync({
                    folder: options.folder || currentFolder().path
                }, done);
            }


            function done(err) {
                if (err && err.code === 409) {
                    // sync still busy
                    return;
                }

                if (err && err.code === 42) {
                    // offline
                    updateStatus('Offline mode');
                    $scope.$apply();
                    return;
                }

                if (err) {
                    updateStatus('Error on sync!');
                    $scope.onError(err);
                    return;
                }

                // display last update
                updateStatus('Last update: ', new Date());

                // do not change the selection if we just updated another folder in the background
                if (currentFolder().path === options.folder) {
                    selectFirstMessage();
                }

                $scope.$apply();

                // fetch visible bodies at the end of a successful sync
                $scope.loadVisibleBodies();
            }
        };

        /**
         * Delete an email by moving it to the trash folder or purging it.
         */
        $scope.remove = function(email) {
            if (!email) {
                return;
            }

            if (currentFolder().type === 'Outbox') {
                $scope.onError({
                    errMsg: 'Deleting messages from the outbox is not yet supported.'
                });
                return;
            }

            removeAndShowNext();
            $scope.synchronize();

            function removeAndShowNext() {
                var index = currentFolder().messages.indexOf(email);
                // show the next mail
                if (currentFolder().messages.length > 1) {
                    // if we're about to delete the last entry of the array, show the previous (i.e. the one below in the list),
                    // otherwise show the next one (i.e. the one above in the list)
                    $scope.select(_.last(currentFolder().messages) === email ? currentFolder().messages[index - 1] : currentFolder().messages[index + 1]);
                } else {
                    // if we have only one email in the array, show nothing
                    $scope.select();
                    $scope.state.mailList.selected = undefined;
                }
                currentFolder().messages.splice(index, 1);
            }
        };

        // share local scope functions with root state
        $scope.state.mailList = {
            remove: $scope.remove,
            synchronize: $scope.synchronize
        };

        //
        // watch tasks
        //

        /**
         * List emails from folder when user changes folder
         */
        $scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() {
            if (!currentFolder()) {
                return;
            }

            // development... display dummy mail objects
            if (!window.chrome || !chrome.identity) {
                updateStatus('Last update: ', new Date());
                currentFolder().messages = createDummyMails();
                selectFirstMessage();
                return;
            }

            // production... in chrome packaged app

            // unselect selection from old folder
            $scope.select();
            // display and select first
            selectFirstMessage();

            $scope.synchronize();
        });

        /**
         * Sync current folder when client comes back online
         */
        $scope.$watch('account.online', function(isOnline) {
            if (isOnline) {
                $scope.synchronize();
            } else {
                updateStatus('Offline mode');
            }
        }, true);

        //
        // helper functions
        //

        function updateStatus(lbl, time) {
            $scope.lastUpdateLbl = lbl;
            $scope.lastUpdate = (time) ? time : '';
        }

        function selectFirstMessage() {
            // wait until after first $digest() so $scope.filteredMessages is set
            $timeout(function() {
                var emails = $scope.filteredMessages;

                if (!emails || emails.length < 1) {
                    $scope.select();
                    return;
                }

                if (!$scope.state.mailList.selected) {
                    // select first message
                    $scope.select(emails[0]);
                }
            });
        }

        function currentFolder() {
            return $scope.state.nav.currentFolder;
        }
    };