2
0
Эх сурвалжийг харах

Indicate to the user when there are unread messages

further down in the chat box.
JC Brand 9 жил өмнө
parent
commit
c738d085c4

+ 16 - 1
css/converse.css

@@ -1369,6 +1369,16 @@
       color: #FB5D50; }
     #conversejs .chatbox .chat-body .delayed .chat-msg-me {
       color: #7EABBB; }
+  #conversejs .chatbox .new-msgs-indicator {
+    position: absolute;
+    width: 100%;
+    cursor: pointer;
+    background-color: #F4A261;
+    color: #FCFDFD;
+    padding: 0.3em;
+    font-size: 0.9em;
+    text-align: center;
+    z-index: 20; }
   #conversejs .chatbox .chat-content {
     position: relative;
     padding: 0.5em;
@@ -1455,7 +1465,7 @@
         box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
         display: none;
         font-size: 12px;
-        margin: 0 0 1px 0;
+        margin: 0;
         position: absolute;
         right: 0; }
         #conversejs .chatbox form.sendXMPPMessage .chat-toolbar ul li {
@@ -1985,10 +1995,15 @@
         max-width: 70%;
         float: left;
         min-width: 200px; }
+        #conversejs .chatroom .box-flyout .chatroom-body .chat-area .new-msgs-indicator {
+          background-color: #E76F51;
+          max-width: 70%; }
         #conversejs .chatroom .box-flyout .chatroom-body .chat-area .chat-content {
           padding: 0 0.5em 0 0.5em; }
         #conversejs .chatroom .box-flyout .chatroom-body .chat-area.full {
           max-width: 100%; }
+          #conversejs .chatroom .box-flyout .chatroom-body .chat-area.full .new-msgs-indicator {
+            max-width: 100%; }
       #conversejs .chatroom .box-flyout .chatroom-body .mentioned {
         font-weight: bold; }
       #conversejs .chatroom .box-flyout .chatroom-body .chat-msg-room {

+ 12 - 1
sass/_chatbox.scss

@@ -187,6 +187,17 @@
                 }
             }
         }
+        .new-msgs-indicator {
+            position: absolute;
+            width: 100%;
+            cursor: pointer;
+            background-color: $chat-head-color;
+            color: $light-background-color;
+            padding: 0.3em;
+            font-size: 0.9em;
+            text-align: center;
+            z-index: 20;
+        }
         .chat-content {
             position: relative;
             padding: 0.5em;
@@ -283,7 +294,7 @@
                     box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
                     display: none;
                     font-size: 12px;
-                    margin: 0 0 1px 0;
+                    margin: 0;
                     position: absolute;
                     right: 0;
                     li {

+ 7 - 0
sass/_chatrooms.scss

@@ -50,12 +50,19 @@
                     max-width: 70%;
                     float: left;
                     min-width: $chat-width;
+                    .new-msgs-indicator {
+                        background-color: $chatroom-head-color;
+                        max-width: 70%;
+                    }
                     .chat-content {
                         // There's an annoying Chrome box-sizing bug which prevents us from adding 0.5em padding here.
                         padding: 0 0.5em 0 0.5em;
                     }
                     &.full {
                         max-width: 100%;
+                        .new-msgs-indicator {
+                            max-width: 100%;
+                        }
                     }
                 }
                 .mentioned {

+ 138 - 91
spec/chatbox.js

@@ -413,10 +413,146 @@
                     runs(function () {});
                 });
 
-
                 describe("when received from someone else", function () {
+                    it("can be received which will open a chatbox and be displayed inside it", function () {
+                        spyOn(converse, 'emit');
+                        var message = 'This is a received message';
+                        var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                        var msg = $msg({
+                                from: sender_jid,
+                                to: this.connection.jid,
+                                type: 'chat',
+                                id: (new Date()).getTime()
+                            }).c('body').t(message).up()
+                            .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
+
+                        // We don't already have an open chatbox for this user
+                        expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
+
+                        runs(function () {
+                            // onMessage is a handler for received XMPP messages
+                            this.chatboxes.onMessage(msg);
+                            expect(converse.emit).toHaveBeenCalledWith('message', msg);
+                        }.bind(converse));
+                        waits(50);
+                        runs(function () {
+                            // Check that the chatbox and its view now exist
+                            var chatbox = this.chatboxes.get(sender_jid);
+                            var chatboxview = this.chatboxviews.get(sender_jid);
+                            expect(chatbox).toBeDefined();
+                            expect(chatboxview).toBeDefined();
+                            // Check that the message was received and check the message parameters
+                            expect(chatbox.messages.length).toEqual(1);
+                            var msg_obj = chatbox.messages.models[0];
+                            expect(msg_obj.get('message')).toEqual(message);
+                            expect(msg_obj.get('fullname')).toEqual(mock.cur_names[0]);
+                            expect(msg_obj.get('sender')).toEqual('them');
+                            expect(msg_obj.get('delayed')).toEqual(false);
+                            // Now check that the message appears inside the chatbox in the DOM
+                            var $chat_content = chatboxview.$el.find('.chat-content');
+                            var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
+                            expect(msg_txt).toEqual(message);
+                            var sender_txt = $chat_content.find('span.chat-msg-them').text();
+                            expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
+                        }.bind(converse));
+                    }.bind(converse));
+
                     it("will cause the chat area to be scrolled down only if it was at the bottom already", function () {
-                        // TODO
+                        var message = 'This message is received while the chat area is scrolled up';
+                        var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                        test_utils.openChatBoxFor(sender_jid);
+                        var chatboxview = converse.chatboxviews.get(sender_jid);
+                        spyOn(chatboxview, 'scrollDown').andCallThrough();
+                        runs(function () {
+                            /* Create enough messages so that there's a
+                             * scrollbar.
+                             */
+                            for (var i=0; i<20; i++) {
+                                converse.chatboxes.onMessage($msg({
+                                        from: sender_jid,
+                                        to: converse.connection.jid,
+                                        type: 'chat',
+                                        id: (new Date()).getTime()
+                                    }).c('body').t('Message: '+i).up()
+                                    .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
+                            }
+                        });
+                        waits(50);
+                        runs(function () {
+                            chatboxview.$content.scrollTop(0);
+                        });
+                        waits(250);
+                        runs(function () {
+                            converse.chatboxes.onMessage($msg({
+                                    from: sender_jid,
+                                    to: converse.connection.jid,
+                                    type: 'chat',
+                                    id: (new Date()).getTime()
+                                }).c('body').t(message).up()
+                                .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
+                        });
+                        waits(150);
+                        runs(function () {
+                            // Now check that the message appears inside the chatbox in the DOM
+                            var $chat_content = chatboxview.$el.find('.chat-content');
+                            var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
+                            expect(msg_txt).toEqual(message);
+                            expect(chatboxview.model.get('scrolled')).toBeTruthy();
+                            expect(chatboxview.$content.scrollTop()).toBe(0);
+                            expect(chatboxview.$('.new-msgs-indicator').is(':visible')).toBeTruthy();
+                            // Scroll down again
+                            chatboxview.$content.scrollTop(chatboxview.$content[0].scrollHeight);
+                        });
+                        waits(250);
+                        runs(function () {
+                            expect(chatboxview.$('.new-msgs-indicator').is(':visible')).toBeFalsy();
+                        });
+                    });
+
+                    it("is ignored if it's intended for a different resource and filter_by_resource is set to true", function () {
+                        // Send a message from a different resource
+                        var message, sender_jid, msg;
+                        spyOn(converse, 'log');
+                        spyOn(converse.chatboxes, 'getChatBox').andCallThrough();
+                        runs(function () {
+                            converse.filter_by_resource = true;
+                            sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+                            msg = $msg({
+                                    from: sender_jid,
+                                    to: converse.bare_jid+'/'+"some-other-resource",
+                                    type: 'chat',
+                                    id: (new Date()).getTime()
+                                }).c('body').t("This message will not be shown").up()
+                                .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
+                            converse.chatboxes.onMessage(msg);
+                        });
+                        waits(50);
+                        runs(function () {
+                            expect(converse.log).toHaveBeenCalledWith(
+                                    "onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource", "info");
+                            expect(converse.chatboxes.getChatBox).not.toHaveBeenCalled();
+                            converse.filter_by_resource = false;
+                        });
+                        waits(50);
+                        runs(function () {
+                            message = "This message sent to a different resource will be shown";
+                            msg = $msg({
+                                    from: sender_jid,
+                                    to: converse.bare_jid+'/'+"some-other-resource",
+                                    type: 'chat',
+                                    id: '134234623462346'
+                                }).c('body').t(message).up()
+                                .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
+                            converse.chatboxes.onMessage(msg);
+                        });
+                        waits(50);
+                        runs(function () {
+                            expect(converse.chatboxes.getChatBox).toHaveBeenCalled();
+                            var chatboxview = converse.chatboxviews.get(sender_jid);
+                            var $chat_content = chatboxview.$el.find('.chat-content:last');
+                            var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
+                            expect(msg_txt).toEqual(message);
+                        });
                     });
                 });
 
@@ -426,95 +562,6 @@
                     });
                 });
 
-                it("can be received which will open a chatbox and be displayed inside it", function () {
-                    spyOn(converse, 'emit');
-                    var message = 'This is a received message';
-                    var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    var msg = $msg({
-                            from: sender_jid,
-                            to: this.connection.jid,
-                            type: 'chat',
-                            id: (new Date()).getTime()
-                        }).c('body').t(message).up()
-                          .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
-
-                    // We don't already have an open chatbox for this user
-                    expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
-
-                    runs(function () {
-                        // onMessage is a handler for received XMPP messages
-                        this.chatboxes.onMessage(msg);
-                        expect(converse.emit).toHaveBeenCalledWith('message', msg);
-                    }.bind(converse));
-                    waits(50);
-                    runs(function () {
-                        // Check that the chatbox and its view now exist
-                        var chatbox = this.chatboxes.get(sender_jid);
-                        var chatboxview = this.chatboxviews.get(sender_jid);
-                        expect(chatbox).toBeDefined();
-                        expect(chatboxview).toBeDefined();
-                        // Check that the message was received and check the message parameters
-                        expect(chatbox.messages.length).toEqual(1);
-                        var msg_obj = chatbox.messages.models[0];
-                        expect(msg_obj.get('message')).toEqual(message);
-                        expect(msg_obj.get('fullname')).toEqual(mock.cur_names[0]);
-                        expect(msg_obj.get('sender')).toEqual('them');
-                        expect(msg_obj.get('delayed')).toEqual(false);
-                        // Now check that the message appears inside the chatbox in the DOM
-                        var $chat_content = chatboxview.$el.find('.chat-content');
-                        var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
-                        expect(msg_txt).toEqual(message);
-                        var sender_txt = $chat_content.find('span.chat-msg-them').text();
-                        expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
-                    }.bind(converse));
-                }.bind(converse));
-
-                it("is ignored if it's intended for a different resource and filter_by_resource is set to true", function () {
-                    // Send a message from a different resource
-                    var message, sender_jid, msg;
-                    spyOn(converse, 'log');
-                    spyOn(converse.chatboxes, 'getChatBox').andCallThrough();
-                    runs(function () {
-                        converse.filter_by_resource = true;
-                        sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                        msg = $msg({
-                                from: sender_jid,
-                                to: converse.bare_jid+'/'+"some-other-resource",
-                                type: 'chat',
-                                id: (new Date()).getTime()
-                            }).c('body').t("This message will not be shown").up()
-                            .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
-                        converse.chatboxes.onMessage(msg);
-                    });
-                    waits(50);
-                    runs(function () {
-                        expect(converse.log).toHaveBeenCalledWith(
-                                "onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource", "info");
-                        expect(converse.chatboxes.getChatBox).not.toHaveBeenCalled();
-                        converse.filter_by_resource = false;
-                    });
-                    waits(50);
-                    runs(function () {
-                        message = "This message sent to a different resource will be shown";
-                        msg = $msg({
-                                from: sender_jid,
-                                to: converse.bare_jid+'/'+"some-other-resource",
-                                type: 'chat',
-                                id: '134234623462346'
-                            }).c('body').t(message).up()
-                            .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
-                        converse.chatboxes.onMessage(msg);
-                    });
-                    waits(50);
-                    runs(function () {
-                        expect(converse.chatboxes.getChatBox).toHaveBeenCalled();
-                        var chatboxview = converse.chatboxviews.get(sender_jid);
-                        var $chat_content = chatboxview.$el.find('.chat-content:last');
-                        var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
-                        expect(msg_txt).toEqual(message);
-                    });
-                });
-
                 it("is ignored if it's a malformed headline message", function () {
                     /* Ideally we wouldn't have to filter out headline
                      * messages, but Prosody gives them the wrong 'type' :(

+ 46 - 2
spec/chatroom.js

@@ -216,6 +216,50 @@
                 expect(converse.emit.callCount, 1);
             });
 
+            it("will cause the chat area to be scrolled down only if it was at the bottom already", function () {
+                var message = 'This message is received while the chat area is scrolled up';
+                test_utils.openChatRoom('lounge', 'localhost', 'dummy');
+                var view = converse.chatboxviews.get('lounge@localhost');
+                spyOn(view, 'scrollDown').andCallThrough();
+                runs(function () {
+                    /* Create enough messages so that there's a
+                        * scrollbar.
+                        */
+                    for (var i=0; i<20; i++) {
+                        converse.chatboxes.onMessage(
+                            $msg({
+                                from: 'lounge@localhost/someone',
+                                to: 'dummy@localhost.com',
+                                type: 'groupchat',
+                                id: (new Date()).getTime(),
+                            }).c('body').t('Message: '+i).tree());
+                    }
+                });
+                waits(50);
+                runs(function () {
+                    view.$content.scrollTop(0);
+                });
+                waits(250);
+                runs(function () {
+                    expect(view.model.get('scrolled')).toBeTruthy();
+                    converse.chatboxes.onMessage(
+                        $msg({
+                            from: 'lounge@localhost/someone',
+                            to: 'dummy@localhost.com',
+                            type: 'groupchat',
+                            id: (new Date()).getTime(),
+                        }).c('body').t(message).tree());
+                });
+                waits(150);
+                runs(function () {
+                    // Now check that the message appears inside the chatbox in the DOM
+                    var $chat_content = view.$el.find('.chat-content');
+                    var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
+                    expect(msg_txt).toEqual(message);
+                    expect(view.$content.scrollTop()).toBe(0);
+                });
+            });
+
             it("shows received chatroom subject messages", function () {
                 var text = 'Jabber/XMPP Development | RFCs and Extensions: http://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org';
                 var stanza = Strophe.xmlHtmlNode(
@@ -461,8 +505,8 @@
                 }.bind(converse));
             }.bind(converse));
         }.bind(converse));
-        
-        
+
+
         describe("Each chat room can take special commands", function () {
             beforeEach(function () {
                 runs(function () {

+ 8 - 2
src/converse-chatview.js

@@ -99,6 +99,7 @@
                                         show_toolbar: converse.show_toolbar,
                                         show_textarea: true,
                                         title: this.model.get('fullname'),
+                                        unread_msgs: __('You have unread messages'),
                                         info_close: __('Close this chat box'),
                                         label_personal_message: __('Personal message')
                                     }
@@ -333,6 +334,9 @@
                         if (converse.windowState === 'blur' || this.model.get('scrolled', true)) {
                             converse.incrementMsgCounter();
                         }
+                        if (this.model.get('scrolled', true)) {
+                            this.$el.find('.new-msgs-indicator').removeClass('hidden');
+                        }
                     } else {
                         // We remove the "scrolled" flag so that the chat area
                         // gets scrolled down. We always want to scroll down
@@ -688,15 +692,16 @@
                     // and the user is scrolled away...
                     // Should probably take a look at incrementMsgCounter
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
-                    var is_at_bottom = this.$content.scrollTop() + this.$content.innerHeight() >= this.$content[0].scrollHeight;
+                    var is_at_bottom = this.$content.scrollTop() + this.$content.innerHeight() >= this.$content[0].scrollHeight-10;
                     if (is_at_bottom) {
                         this.model.set('scrolled', false);
+                        this.$el.find('.new-msgs-indicator').addClass('hidden');
                     } else {
                         // We're not at the bottom of the chat area, so we mark
                         // that the box is in a scrolled-up state.
                         this.model.set('scrolled', true);
                     }
-                }, 50),
+                }, 150),
 
                 scrollDownMessageHeight: function ($message) {
                     if (this.$content.is(':visible') && !this.model.get('scrolled')) {
@@ -708,6 +713,7 @@
                 scrollDown: function () {
                     if (this.$content.is(':visible') && !this.model.get('scrolled')) {
                         this.$content.scrollTop(this.$content[0].scrollHeight);
+                        this.$el.find('.new-msgs-indicator').addClass('hidden');
                     }
                     return this;
                 }

+ 3 - 0
src/converse-core.js

@@ -1577,6 +1577,9 @@
         });
 
         this.setUpXMLLogging = function () {
+            Strophe.log = function (level, msg) {
+                converse.log(msg, level);
+            };
             if (this.debug) {
                 this.connection.xmlInput = function (body) { converse.log(body.outerHTML); };
                 this.connection.xmlOutput = function (body) { converse.log(body.outerHTML); };

+ 1 - 1
src/converse-dragresize.js

@@ -27,7 +27,7 @@
             // relevant objects or classes.
             //
             // New functions which don't exist yet can also be added.
-            
+
             registerGlobalEventHandlers: function () {
                 $(document).on('mousemove', function (ev) {
                     if (!this.resizing || !this.allow_dragresize) { return true; }

+ 2 - 1
src/converse-headline.js

@@ -44,7 +44,7 @@
             // relevant objects or classes.
             //
             // New functions which don't exist yet can also be added.
-            
+
             ChatBoxViews: {
                 onChatBoxAdded: function (item) {
                     var view = this.get(item.get('id'));
@@ -93,6 +93,7 @@
                                         show_toolbar: converse.show_toolbar,
                                         show_textarea: false,
                                         title: this.model.get('fullname'),
+                                        unread_msgs: __('You have unread messages'),
                                         info_close: __('Close this box'),
                                         info_minimize: __('Minimize this box'),
                                         label_personal_message: ''

+ 1 - 0
src/converse-muc.js

@@ -216,6 +216,7 @@
                         this.$('.chatroom-body').empty()
                             .append(
                                 converse.templates.chatarea({
+                                    'unread_msgs': __('You have unread messages'),
                                     'show_toolbar': converse.show_toolbar,
                                     'label_message': __('Message')
                                 }))

+ 1 - 0
src/templates/chatarea.html

@@ -1,5 +1,6 @@
 <div class="chat-area">
     <div class="chat-content"></div>
+    <div class="new-msgs-indicator hidden">▼ {{ unread_msgs }} ▼</div>
     <form class="sendXMPPMessage" action="" method="post">
         {[ if (show_toolbar) { ]}
             <ul class="chat-toolbar no-text-select"></ul>

+ 1 - 0
src/templates/chatbox.html

@@ -17,6 +17,7 @@
     </div>
     <div class="chat-body">
         <div class="chat-content"></div>
+        <div class="new-msgs-indicator hidden">▼ {{ unread_msgs }} ▼</div>
         {[ if (show_textarea) { ]}
         <form class="sendXMPPMessage" action="" method="post">
             {[ if (show_toolbar) { ]}

+ 1 - 1
tests/main.js

@@ -55,7 +55,7 @@ require([
             auto_login: true,
             jid: 'dummy@localhost',
             password: 'secret',
-            debug: false
+            debug: true 
         }, function (converse) {
             window.converse = converse;
             window.crypto = {