diff --git a/js/models/messages.js b/js/models/messages.js index 871e0088b..7611dd3d1 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -357,18 +357,27 @@ }, fetchConversation: function(conversationId) { - var options = {remove: false}; - options.index = { - // 'conversation' index on [conversationId, received_at] - name : 'conversation', - lower : [conversationId], - upper : [conversationId, Number.MAX_VALUE] - // SELECT messages WHERE conversationId = this.id ORDER - // received_at DESC - }; - // TODO pagination/infinite scroll - // limit: 10, offset: page*10, - return this.fetch(options); + return new Promise(function(resolve) { + var upper; + if (this.length === 0) { + // fetch the most recent messages first + upper = Number.MAX_VALUE; + } else { + // not our first rodeo, fetch older messages. + upper = this.at(0).get('received_at'); + } + var options = {remove: false, limit: 100}; + options.index = { + // 'conversation' index on [conversationId, received_at] + name : 'conversation', + lower : [conversationId], + upper : [conversationId, upper], + order : 'desc' + // SELECT messages WHERE conversationId = this.id ORDER + // received_at DESC + }; + this.fetch(options).then(resolve); + }.bind(this)); }, hasKeyConflicts: function() { diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index d5860269b..4c81d5da7 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -64,9 +64,7 @@ this.remove(); }.bind(this)); - setTimeout(function() { - this.view.scrollToBottom(); - }.bind(this), 10); + this.fetchMessages(); }, events: { @@ -84,12 +82,23 @@ 'click' : 'onClick', 'select .message-list .entry': 'messageDetail', 'force-resize': 'forceUpdateMessageFieldSize', - 'click .choose-file': 'focusMessageField' + 'click .choose-file': 'focusMessageField', + 'loadMore .message-list': 'fetchMessages' }, focusMessageField: function() { this.$messageField.focus(); }, + fetchMessages: function() { + this.$('.message-list').addClass('loading'); + return this.model.fetchContacts().then(function() { + return this.model.fetchMessages().then(function() { + this.$('.message-list').removeClass('loading'); + }.bind(this)); + }.bind(this)); + // TODO catch? + }, + addMessage: function(message) { this.model.messageCollection.add(message, {merge: true}); }, diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index 55d710fa0..59bf9dd5e 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -54,18 +54,10 @@ appWindow: this.model.appWindow }); $el = view.$el; - if (conversation.messageCollection.length === 0) { - $el.find('.message-list').addClass('loading'); - } } $el.prependTo(this.el); $el.find('.message-list').trigger('reset-scroll'); $el.trigger('force-resize'); - conversation.fetchContacts().then(function() { - conversation.fetchMessages().then(function() { - $el.find('.message-list').removeClass('loading'); - }); - }); conversation.markRead(); conversation.trigger('opened'); } diff --git a/js/views/message_list_view.js b/js/views/message_list_view.js index a30eaeb6d..049f8d3f8 100644 --- a/js/views/message_list_view.js +++ b/js/views/message_list_view.js @@ -10,14 +10,15 @@ className: 'message-list', itemView: Whisper.MessageView, events: { - 'add': 'onAdd', - 'update *': 'scrollToBottom', - 'scroll': 'measureScrollPosition', + 'update *': 'scrollToBottomIfNeeded', + 'scroll': 'onScroll', 'reset-scroll': 'resetScrollPosition' }, - onAdd: function() { - this.$el.removeClass('loading'); - this.scrollToBottom(); + onScroll: function() { + this.measureScrollPosition(); + if (this.$el.scrollTop() === 0) { + this.$el.trigger('loadMore'); + } }, measureScrollPosition: function() { if (this.el.scrollHeight === 0) { // hidden @@ -47,6 +48,22 @@ addAll: function() { Whisper.ListView.prototype.addAll.apply(this, arguments); // super() this.scrollToBottom(); - } + }, + addOne: function(model) { + if (this.itemView) { + var view = new this.itemView({model: model}).render(); + if (this.collection.indexOf(model) === this.collection.length - 1) { + // add to the bottom. + this.$el.append(view.el); + this.scrollToBottom(); + } else { + // add to the top. + var offset = this.el.scrollHeight - this.$el.scrollTop(); + this.$el.prepend(view.el); + this.$el.scrollTop(this.el.scrollHeight - offset); + } + } + this.$el.removeClass('loading'); + }, }); })(); diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 230780c23..746bf068e 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -219,6 +219,14 @@ } .message-list { + position: relative; + &::before { + display: block; + margin: $header-height auto; + content: " "; + height: $header-height; + width: $header-height; + } margin: 0; padding: 1em 0; overflow-y: auto; diff --git a/stylesheets/_global.scss b/stylesheets/_global.scss index c5442ee2b..ec6f98ae6 100644 --- a/stylesheets/_global.scss +++ b/stylesheets/_global.scss @@ -396,7 +396,7 @@ $avatar-size: 44px; .loading { position: relative; - &::after { + &::before { display: block; margin: $header-height auto; content: " "; diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css index c393c0dd9..9d7e22241 100644 --- a/stylesheets/manifest.css +++ b/stylesheets/manifest.css @@ -315,7 +315,7 @@ img.emoji { .loading { position: relative; } - .loading::after { + .loading::before { display: block; margin: 36px auto; content: " "; @@ -666,9 +666,16 @@ input.search { opacity: 1; } .message-list { + position: relative; margin: 0; padding: 1em 0; overflow-y: auto; } + .message-list::before { + display: block; + margin: 36px auto; + content: " "; + height: 36px; + width: 36px; } .message-list .timestamp { cursor: pointer; } .message-list .timestamp:hover {