diff --git a/src/plugins/chatview/bottom-panel.js b/src/plugins/chatview/bottom-panel.js index b8db9eff1..3bd2cf9d0 100644 --- a/src/plugins/chatview/bottom-panel.js +++ b/src/plugins/chatview/bottom-panel.js @@ -15,17 +15,26 @@ export default class ChatBottomPanel extends ElementView { 'click .toggle-clear': 'clearMessages' }; + constructor () { + super(); + this.debouncedRender = debounce(this.render, 100); + } + async connectedCallback () { super.connectedCallback(); - this.debouncedRender = debounce(this.render, 100); - this.model = _converse.chatboxes.get(this.getAttribute('jid')); + await this.initialize(); + this.render(); // don't call in initialize, since the MUCBottomPanel subclasses it + // and we want to render after it has finished as wel. + } + + async initialize () { + this.model = await api.chatboxes.get(this.getAttribute('jid')); await this.model.initialized; this.listenTo(this.model, 'change:num_unread', this.debouncedRender) this.listenTo(this.model, 'emoji-picker-autocomplete', this.autocompleteInPicker); this.addEventListener('focusin', ev => this.emitFocused(ev)); this.addEventListener('focusout', ev => this.emitBlurred(ev)); - this.render(); } render () { diff --git a/src/plugins/chatview/tests/messages.js b/src/plugins/chatview/tests/messages.js index 5511e020e..8cf874d0b 100644 --- a/src/plugins/chatview/tests/messages.js +++ b/src/plugins/chatview/tests/messages.js @@ -14,8 +14,7 @@ describe("A Chat Message", function () { await mock.openChatBoxFor(_converse, contact_jid); const view = _converse.chatboxviews.get(contact_jid); await _converse.handleMessageStanza(mock.createChatMessage(_converse, contact_jid, 'This message will be read')); - const msg_el = await u.waitUntil(() => view.querySelector('converse-chat-message')); - expect(msg_el.querySelector('.chat-msg__text').textContent).toBe('This message will be read'); + await u.waitUntil(() => view.querySelector('converse-chat-message .chat-msg__text')?.textContent === 'This message will be read'); expect(view.model.get('num_unread')).toBe(0); _converse.windowState = 'hidden'; @@ -26,6 +25,7 @@ describe("A Chat Message", function () { expect(view.model.get('first_unread_id')).toBe(view.model.messages.last().get('id')); await u.waitUntil(() => view.querySelectorAll('converse-chat-message').length === 2); + await u.waitUntil(() => view.querySelector('converse-chat-message:last-child .chat-msg__text')?.textContent === 'This message will be new'); const last_msg_el = view.querySelector('converse-chat-message:last-child'); expect(last_msg_el.firstElementChild?.textContent).toBe('New messages'); done(); diff --git a/src/plugins/muc-views/bottom-panel.js b/src/plugins/muc-views/bottom-panel.js index b1486f433..cdc7bdebe 100644 --- a/src/plugins/muc-views/bottom-panel.js +++ b/src/plugins/muc-views/bottom-panel.js @@ -1,6 +1,5 @@ import 'shared/autocomplete/index.js'; import BottomPanel from 'plugins/chatview/bottom-panel.js'; -import debounce from 'lodash-es/debounce'; import tpl_muc_bottom_panel from './templates/muc-bottom-panel.js'; import { _converse, api, converse } from "@converse/headless/core"; import { render } from 'lit'; @@ -15,17 +14,14 @@ export default class MUCBottomPanel extends BottomPanel { 'click .send-button': 'sendButtonClicked', } - async connectedCallback () { - // this.model gets set in the super method and we also wait there for this.model.initialized - await super.connectedCallback(); - this.debouncedRender = debounce(this.render, 100); + async initialize () { + await super.initialize(); this.listenTo(this.model, 'change:hidden_occupants', this.debouncedRender); this.listenTo(this.model, 'change:num_unread_general', this.debouncedRender) this.listenTo(this.model.features, 'change:moderated', this.debouncedRender); this.listenTo(this.model.occupants, 'add', this.renderIfOwnOccupant) this.listenTo(this.model.occupants, 'change:role', this.renderIfOwnOccupant); this.listenTo(this.model.session, 'change:connection_status', this.debouncedRender); - this.render(); } render () { diff --git a/src/plugins/muc-views/tests/emojis.js b/src/plugins/muc-views/tests/emojis.js index 77113c285..fa85ca463 100644 --- a/src/plugins/muc-views/tests/emojis.js +++ b/src/plugins/muc-views/tests/emojis.js @@ -12,7 +12,7 @@ describe("Emojis", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - await u.waitUntil(() => view.querySelector('converse-emoji-dropdown')); + await u.waitUntil(() => view.querySelector('converse-emoji-picker')); const textarea = view.querySelector('textarea.chat-textarea'); textarea.value = ':gri'; @@ -80,7 +80,7 @@ describe("Emojis", function () { await mock.waitForRoster(_converse, 'current', 0); await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - await u.waitUntil(() => view.querySelector('converse-emoji-dropdown')); + await u.waitUntil(() => view.querySelector('converse-emoji-picker')); const textarea = view.querySelector('textarea.chat-textarea'); textarea.value = ':'; // Press tab @@ -130,7 +130,7 @@ describe("Emojis", function () { await mock.waitForRoster(_converse, 'current', 0); await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - await u.waitUntil(() => view.querySelector('converse-emoji-dropdown')); + await u.waitUntil(() => view.querySelector('converse-emoji-picker')); const textarea = view.querySelector('textarea.chat-textarea'); textarea.value = ':gri'; diff --git a/src/shared/chat/chat-content.js b/src/shared/chat/chat-content.js index e46d1fb80..32eec0402 100644 --- a/src/shared/chat/chat-content.js +++ b/src/shared/chat/chat-content.js @@ -1,6 +1,6 @@ import './message-history'; import { CustomElement } from 'shared/components/element.js'; -import { _converse, api } from '@converse/headless/core'; +import { api } from '@converse/headless/core'; import { html } from 'lit'; import { markScrolled } from './utils.js'; @@ -25,8 +25,8 @@ export default class ChatContent extends CustomElement { this.removeEventListener('scroll', markScrolled); } - initialize () { - this.model = _converse.chatboxes.get(this.jid); + async initialize () { + await this.setModels(); this.listenTo(this.model, 'change:hidden_occupants', this.requestUpdate); this.listenTo(this.model.messages, 'add', this.requestUpdate); this.listenTo(this.model.messages, 'change', this.requestUpdate); @@ -43,7 +43,16 @@ export default class ChatContent extends CustomElement { this.addEventListener('scroll', markScrolled); } + async setModels () { + this.model = await api.chatboxes.get(this.jid); + await this.model.initialized; + this.requestUpdate(); + } + render () { + if (!this.model) { + return ''; + } // This element has "flex-direction: reverse", so elements here are // shown in reverse order. return html` diff --git a/src/shared/chat/message.js b/src/shared/chat/message.js index 3cd129e8c..c34a5f738 100644 --- a/src/shared/chat/message.js +++ b/src/shared/chat/message.js @@ -28,23 +28,13 @@ export default class Message extends CustomElement { } } - render () { - if (this.show_spinner) { - return tpl_spinner(); - } else if (this.model.get('file') && !this.model.get('oob_url')) { - return this.renderFileProgress(); - } else if (['error', 'info'].includes(this.model.get('type'))) { - return this.renderInfoMessage(); - } else { - return this.renderChatMessage(); - } - } - connectedCallback () { super.connectedCallback(); - this.chatbox = _converse.chatboxes.get(this.jid); - this.model = this.chatbox.messages.get(this.mid); + this.initialize(); + } + async initialize () { + await this.setModels(); this.listenTo(this.chatbox, 'change:first_unread_id', this.requestUpdate); this.listenTo(this.model, 'change', this.requestUpdate); this.model.vcard && this.listenTo(this.model.vcard, 'change', this.requestUpdate); @@ -60,6 +50,27 @@ export default class Message extends CustomElement { } } + async setModels () { + this.chatbox = await api.chatboxes.get(this.jid); + await this.chatbox.initialized; + this.model = this.chatbox.messages.get(this.mid); + this.requestUpdate(); + } + + render () { + if (!this.model) { + return ''; + } else if (this.show_spinner) { + return tpl_spinner(); + } else if (this.model.get('file') && !this.model.get('oob_url')) { + return this.renderFileProgress(); + } else if (['error', 'info'].includes(this.model.get('type'))) { + return this.renderInfoMessage(); + } else { + return this.renderChatMessage(); + } + } + getProps () { return Object.assign( this.model.toJSON(),