소스 검색

Maintain scroll position when re-inserting #conversejs element

JC Brand 5 년 전
부모
커밋
4927d561a5
4개의 변경된 파일47개의 추가작업 그리고 36개의 파일을 삭제
  1. 4 0
      src/converse-chatboxviews.js
  2. 40 33
      src/converse-chatview.js
  3. 1 1
      src/converse-muc-views.js
  4. 2 2
      src/templates/directives/body.js

+ 4 - 0
src/converse-chatboxviews.js

@@ -176,6 +176,10 @@ converse.plugins.add('converse-chatboxviews', {
                 const el = _converse.chatboxviews?.el;
                 if (el && !container.contains(el)) {
                     container.insertAdjacentElement('afterBegin', el);
+                    api.chatviews.get()
+                        .filter(v => v.model.get('id') !== 'controlbox')
+                        .forEach(v => v.maintainScrollTop());
+
                 } else if (!el) {
                     throw new Error("Cannot insert non-existing #conversejs element into the DOM");
                 }

+ 40 - 33
src/converse-chatview.js

@@ -208,7 +208,7 @@ converse.plugins.add('converse-chatview', {
                 this.listenTo(this.model.messages, 'add', this.onMessageAdded);
                 this.listenTo(this.model.messages, 'change', this.renderChatHistory);
                 this.listenTo(this.model.messages, 'remove', this.renderChatHistory);
-                this.listenTo(this.model.messages, 'rendered', this.maybeScrollDownOnMessage);
+                this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown);
                 this.listenTo(this.model.messages, 'reset', this.renderChatHistory);
                 this.listenTo(this.model.notifications, 'change', this.renderNotifications);
                 this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages);
@@ -241,7 +241,7 @@ converse.plugins.add('converse-chatview', {
 
             render () {
                 const result = tpl_chatbox(
-                    Object.assign(this.model.toJSON(), {'markScrolled': () => this.markScrolled()})
+                    Object.assign(this.model.toJSON(), {'markScrolled': ev => this.markScrolled(ev)})
                 );
                 render(result, this.el);
                 this.content = this.el.querySelector('.chat-content');
@@ -486,19 +486,35 @@ converse.plugins.add('converse-chatview', {
                 api.trigger('afterMessagesFetched', this.model);
             },
 
-            maybeScrollDownOnMessage (message) {
-                if (message.get('sender') === 'me' || !this.model.get('scrolled')) {
+            /**
+             * Scrolls the chat down, *if* appropriate.
+             *
+             * Will only scroll down if we have received a message from
+             * ourselves, or if the chat was scrolled down before (i.e. the
+             * `scrolled` flag is `false`);
+             * @param { _converse.Message|_converse.ChatRoomMessage } [message]
+             *  - An optional message that serves as the cause for needing to scroll down.
+             */
+            maybeScrollDown (message) {
+                if (message?.get('sender') === 'me' || !this.model.get('scrolled')) {
                     this.debouncedScrollDown();
                 }
             },
 
+            /**
+             * Scrolls the chat down.
+             *
+             * This method will always scroll the chat down, regardless of
+             * whether the user scrolled up manually or not.
+             * @param { Event } [ev] - An optional event that is the cause for needing to scroll down.
+             */
             scrollDown (ev) {
                 ev?.preventDefault?.();
                 ev?.stopPropagation?.();
                 if (this.model.get('scrolled')) {
                     u.safeSave(this.model, {
                         'scrolled': false,
-                        'top_visible_message': null,
+                        'scrollTop': null,
                     });
                 }
                 if (this.msgs_container.scrollTo) {
@@ -510,6 +526,19 @@ converse.plugins.add('converse-chatview', {
                 this.onScrolledDown();
             },
 
+            /**
+             * Scroll to the previously saved scrollTop position, or scroll
+             * down if it wasn't set.
+             */
+            maintainScrollTop () {
+                const pos = this.model.get('scrollTop');
+                if (pos) {
+                    this.msgs_container.scrollTop = pos;
+                } else {
+                    this.scrollDown();
+                }
+            },
+
             insertIntoDOM () {
                 _converse.chatboxviews.insertRowColumn(this.el);
                 /**
@@ -537,28 +566,6 @@ converse.plugins.add('converse-chatview', {
                 this.content.querySelectorAll('.spinner').forEach(u.removeElement);
             },
 
-            setScrollPosition (message_el) {
-                /* Given a newly inserted message, determine whether we
-                 * should keep the scrollbar in place (so as to not scroll
-                 * up when using infinite scroll).
-                 */
-                if (this.model.get('scrolled')) {
-                    const next_msg_el = u.getNextElement(message_el, ".chat-msg");
-                    if (next_msg_el) {
-                        // The currently received message is not new, there
-                        // are newer messages after it. So let's see if we
-                        // should maintain our current scroll position.
-                        if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) {
-                            const top_visible_message = this.model.get('top_visible_message') || next_msg_el;
-                            this.model.set('top_visible_message', top_visible_message);
-                            this.content.scrollTop = top_visible_message.offsetTop - 30;
-                        }
-                    }
-                } else {
-                    this.scrollDown();
-                }
-            },
-
             onStatusMessageChanged (item) {
                 this.renderHeading();
                 /**
@@ -1091,8 +1098,9 @@ converse.plugins.add('converse-chatview', {
              * which debounces this method by 100ms.
              * @private
              */
-            _markScrolled: function () {
+            _markScrolled: function (ev) {
                 let scrolled = true;
+                let scrollTop = null;
                 const is_at_bottom =
                     (this.msgs_container.scrollTop + this.msgs_container.clientHeight) >=
                         this.msgs_container.scrollHeight - 62; // sigh...
@@ -1108,15 +1116,14 @@ converse.plugins.add('converse-chatview', {
                      * @example _converse.api.listen.on('chatBoxScrolledUp', obj => { ... });
                      */
                     api.trigger('chatBoxScrolledUp', this);
+                } else {
+                    scrollTop = ev.target.scrollTop;
                 }
-                u.safeSave(this.model, {
-                    'scrolled': scrolled,
-                    'top_visible_message': null
-                });
+                u.safeSave(this.model, { scrolled, scrollTop });
             },
 
             viewUnreadMessages () {
-                this.model.save({'scrolled': false, 'top_visible_message': null});
+                this.model.save({'scrolled': false, 'scrollTop': null});
                 this.scrollDown();
             },
 

+ 1 - 1
src/converse-muc-views.js

@@ -190,7 +190,7 @@ converse.plugins.add('converse-muc-views', {
                 this.listenTo(this.model, 'show', this.show);
                 this.listenTo(this.model.features, 'change:moderated', this.renderBottomPanel);
                 this.listenTo(this.model.features, 'change:open', this.renderHeading);
-                this.listenTo(this.model.messages, 'rendered', this.maybeScrollDownOnMessage);
+                this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown);
                 this.listenTo(this.model.session, 'change:connection_status', this.onConnectionStatusChanged);
 
                 // Bind so that we can pass it to addEventListener and removeEventListener

+ 2 - 2
src/templates/directives/body.js

@@ -168,12 +168,12 @@ class MessageBodyRenderer {
         // image loads, it triggers 'scroll' and the chat will be marked as scrolled,
         // which is technically true, but not what we want because the user
         // didn't initiate the scrolling.
-        this.scrolled = this.chatview.model.get('scrolled');
+        this.was_scrolled_up = this.chatview.model.get('scrolled');
         this.text = this.component.model.getMessageText();
     }
 
     scrollDownOnImageLoad () {
-        if (!this.scrolled) {
+        if (!this.was_scrolled_up) {
             this.chatview.scrollDown();
         }
     }