Browse Source

New config option: message_history_size

JC Brand 5 năm trước cách đây
mục cha
commit
76281d37fb
5 tập tin đã thay đổi với 187 bổ sung1 xóa
  1. 2 1
      CHANGES.md
  2. 12 0
      docs/source/configuration.rst
  3. 81 0
      spec/chatbox.js
  4. 81 0
      spec/muc.js
  5. 11 0
      src/headless/converse-chat.js

+ 2 - 1
CHANGES.md

@@ -18,8 +18,9 @@
 - #1839: Headline messages are shown in controlbox
 - Allow ignore bootstrap modules at build using environment variable: BOOTSTRAP_IGNORE_MODULES="Modal,Dropdown".
   example: export BOOTSTRAP_IGNORE_MODULES="Modal,Dropdown" && make dist
-- New config option [modtools_disable_query](https://conversejs.org/docs/html/configuration.html#modtools-disable-query)
+- New config option [message_history_size](https://conversejs.org/docs/html/configuration.html#message-history-size)
 - New config option [modtools_disable_assign](https://conversejs.org/docs/html/configuration.html#modtools-disable-assign)
+- New config option [modtools_disable_query](https://conversejs.org/docs/html/configuration.html#modtools-disable-query)
 
 ## 6.0.0 (2020-01-09)
 

+ 12 - 0
docs/source/configuration.rst

@@ -1027,6 +1027,18 @@ tab serves as a separate IM client.
 XEP-0280 requires server support, so make sure that message carbons are enabled
 on your server.
 
+message_history_size
+--------------------
+
+* Default: ``100``
+
+Determines how many mesages are shown in a chat's history. Set this to zero to
+disable any limit.
+
+Having too many messages in the DOM can cause performance issues such as sluggishness.
+We're working on adding virtualized lists to overcome this. In the meantime
+this setting is used to avoid too many messages in the DOM.
+
 
 message_limit
 -------------

+ 81 - 0
spec/chatbox.js

@@ -406,6 +406,87 @@
                 done();
             }));
 
+            it("limits the amount of messages in the DOM based on the message_history_size config setting",
+                mock.initConverse(
+                    ['rosterGroupsFetched'], {'clear_messages_on_reconnection': true, 'message_history_size': 3},
+                    async function (done, _converse) {
+
+                await test_utils.waitForRoster(_converse, 'current');
+                const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
+                await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
+                await test_utils.openChatBoxFor(_converse, contact_jid);
+                const view = _converse.chatboxviews.get(contact_jid);
+
+                const promises = [];
+                for (let i=0; i<10; i++) {
+                    const msg = $msg({
+                        'from': contact_jid,
+                        'id': u.getUniqueId(),
+                        'to': _converse.bare_jid,
+                        'type': 'chat'
+                    }).c('body').t(`Message ${i}`).tree();
+                    promises.push(view.model.onMessage(msg));
+                }
+                await Promise.all(promises);
+                expect(view.model.messages.length).toBe(3);
+                expect(view.model.messages.at(0).get('message')).toBe('Message 7');
+                expect(view.model.messages.at(1).get('message')).toBe('Message 8');
+                expect(view.model.messages.at(2).get('message')).toBe('Message 9');
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
+
+                let msg = $msg({
+                    'from': contact_jid,
+                    'id': u.getUniqueId(),
+                    'to': 'romeo@montague.lit',
+                    'type': 'chat'
+                }).c('body').t(`Another message`).tree();
+                await view.model.onMessage(msg);
+                expect(view.model.messages.length).toBe(3);
+                expect(view.model.messages.at(0).get('message')).toBe('Message 8');
+                expect(view.model.messages.at(1).get('message')).toBe('Message 9');
+                expect(view.model.messages.at(2).get('message')).toBe('Another message');
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
+
+                await test_utils.sendMessage(view, 'hello world');
+                expect(view.model.messages.length).toBe(3);
+                expect(view.model.messages.at(0).get('message')).toBe('Message 9');
+                expect(view.model.messages.at(1).get('message')).toBe('Another message');
+                expect(view.model.messages.at(2).get('message')).toBe('hello world');
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
+
+                // chat-state messages don't trigger deletions
+                msg = $msg({
+                    'from': contact_jid,
+                    'id': u.getUniqueId(),
+                    'to': 'romeo@montague.lit',
+                    'type': 'chat'
+                }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
+
+                await view.model.onMessage(msg);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
+                expect(view.model.messages.length).toBe(4);
+                expect(view.model.messages.at(0).get('message')).toBe('Message 9');
+                expect(view.model.messages.at(1).get('message')).toBe('Another message');
+                expect(view.model.messages.at(2).get('message')).toBe('hello world');
+                expect(u.isOnlyChatStateNotification(view.model.messages.at(3))).toBe(true);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
+
+                msg = $msg({
+                    'from': contact_jid,
+                    'id': u.getUniqueId(),
+                    'to': 'romeo@montague.lit',
+                    'type': 'chat'
+                }).c('body').t(`Yet another message`).tree();
+                await view.model.onMessage(msg);
+                expect(view.model.messages.length).toBe(4);
+                expect(view.model.messages.at(0).get('message')).toBe('Another message');
+                expect(view.model.messages.at(1).get('message')).toBe('hello world');
+                expect(u.isOnlyChatStateNotification(view.model.messages.at(2))).toBe(true);
+                expect(view.model.messages.at(3).get('message')).toBe('Yet another message');
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
+                done()
+            }));
+
             describe("A chat toolbar", function () {
 
                 it("can be found on each chat box",

+ 81 - 0
spec/muc.js

@@ -499,6 +499,87 @@
                 });
             });
 
+
+            it("limits the amount of messages in the DOM based on the message_history_size config setting",
+                mock.initConverse(
+                    ['rosterGroupsFetched'], {'clear_messages_on_reconnection': true, 'message_history_size': 3},
+                    async function (done, _converse) {
+
+                const nick = mock.chatroom_names[0];
+                const muc_jid = 'lounge@montague.lit';
+                await test_utils.openAndEnterChatRoom(_converse, muc_jid , 'romeo');
+                const view = _converse.chatboxviews.get(muc_jid);
+                const promises = [];
+                for (let i=0; i<10; i++) {
+                    const msg = $msg({
+                        'from': `${muc_jid}/${nick}`,
+                        'id': u.getUniqueId(),
+                        'to': 'romeo@montague.lit',
+                        'type': 'groupchat'
+                    }).c('body').t(`Message ${i}`).tree();
+                    promises.push(view.model.onMessage(msg));
+                }
+                await Promise.all(promises);
+                expect(view.model.messages.length).toBe(3);
+                expect(view.model.messages.at(0).get('message')).toBe('Message 7');
+                expect(view.model.messages.at(1).get('message')).toBe('Message 8');
+                expect(view.model.messages.at(2).get('message')).toBe('Message 9');
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
+
+                let msg = $msg({
+                    'from': `${muc_jid}/${mock.chatroom_names[1]}`,
+                    'id': u.getUniqueId(),
+                    'to': 'romeo@montague.lit',
+                    'type': 'groupchat'
+                }).c('body').t(`Another message`).tree();
+                await view.model.onMessage(msg);
+                expect(view.model.messages.length).toBe(3);
+                expect(view.model.messages.at(0).get('message')).toBe('Message 8');
+                expect(view.model.messages.at(1).get('message')).toBe('Message 9');
+                expect(view.model.messages.at(2).get('message')).toBe('Another message');
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3);
+
+                await test_utils.sendMessage(view, 'hello world');
+                expect(view.model.messages.length).toBe(3);
+                expect(view.model.messages.at(0).get('message')).toBe('Message 9');
+                expect(view.model.messages.at(1).get('message')).toBe('Another message');
+                expect(view.model.messages.at(2).get('message')).toBe('hello world');
+                expect(view.el.querySelectorAll('.chat-msg__text').length).toBe(3);
+
+                // chat-state messages don't trigger deletions
+                msg = $msg({
+                        from: muc_jid+'/newguy',
+                        id: u.getUniqueId(),
+                        to: 'romeo@montague.lit',
+                        type: 'groupchat'
+                    }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
+
+                await view.model.onMessage(msg);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
+                expect(view.model.messages.length).toBe(4);
+                expect(view.model.messages.at(0).get('message')).toBe('Message 9');
+                expect(view.model.messages.at(1).get('message')).toBe('Another message');
+                expect(view.model.messages.at(2).get('message')).toBe('hello world');
+                expect(u.isOnlyChatStateNotification(view.model.messages.at(3))).toBe(true);
+                expect(view.el.querySelectorAll('.chat-msg__text').length).toBe(3);
+
+                msg = $msg({
+                    'from': `${muc_jid}/${mock.chatroom_names[1]}`,
+                    'id': u.getUniqueId(),
+                    'to': 'romeo@montague.lit',
+                    'type': 'groupchat'
+                }).c('body').t(`Yet another message`).tree();
+                await view.model.onMessage(msg);
+                expect(view.model.messages.length).toBe(4);
+                expect(view.model.messages.at(0).get('message')).toBe('Another message');
+                expect(view.model.messages.at(1).get('message')).toBe('hello world');
+                expect(u.isOnlyChatStateNotification(view.model.messages.at(2))).toBe(true);
+                expect(view.model.messages.at(3).get('message')).toBe('Yet another message');
+                expect(view.el.querySelectorAll('.chat-msg__text').length).toBe(3);
+                done()
+            }));
+
+
             it("clears cached messages when it gets closed and clear_messages_on_reconnection is true",
                 mock.initConverse(
                     ['rosterGroupsFetched'], {'clear_messages_on_reconnection': true},

+ 11 - 0
src/headless/converse-chat.js

@@ -42,6 +42,7 @@ converse.plugins.add('converse-chat', {
         // Refer to docs/source/configuration.rst for explanations of these
         // configuration settings.
         _converse.api.settings.update({
+            'message_history_size': 0,
             'auto_join_private_chats': [],
             'clear_messages_on_reconnection': false,
             'filter_by_resource': false,
@@ -340,6 +341,16 @@ converse.plugins.add('converse-chat', {
                         _converse.api.send(this.createMessageStanza(message));
                     }
                 });
+                if (_converse.message_history_size) {
+                    this.listenTo(this.messages, 'add', message => {
+                        if (!u.isEmptyMessage(message) && this.messages.length > _converse.message_history_size) {
+                            const non_empty_messages = this.messages.filter(m => !u.isEmptyMessage(m));
+                            while (non_empty_messages.length > _converse.message_history_size) {
+                                non_empty_messages.shift().destroy();
+                            }
+                        }
+                    });
+                }
             },
 
             afterMessagesFetched () {