Don't render hidden chats

This commit is contained in:
JC Brand 2021-03-11 12:33:50 +01:00
parent 2dbe50fc97
commit 4646956922
12 changed files with 88 additions and 72 deletions

View File

@ -32,6 +32,9 @@ Removed events:
* `bookmarkViewsInitialized`
* `rosterGroupsFetched`
The `chatBoxMaximized` and `chatBoxMinimized` events now have the `model` as
payload and not the `view` since it might not be exist at that time.
## 7.0.5 (Unreleased)
- #2377: The @converse/headless NPM package is missing the dist directory, causing import errors

View File

@ -119,6 +119,7 @@ describe("Chatboxes", function () {
await new Promise(resolve => _converse.api.listen.once('chatBoxViewInitialized', resolve));
await u.waitUntil(() => message_promise);
expect(_converse.chatboxviews.keys().length).toBe(2);
expect(_converse.chatboxviews.keys().pop()).toBe(sender_jid);
done();
}));
@ -198,6 +199,7 @@ describe("Chatboxes", function () {
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => rosterview.querySelectorAll('.roster-group').length);
@ -372,8 +374,8 @@ describe("Chatboxes", function () {
u.waitUntil(() => rosterview.querySelectorAll('.roster-group').length);
spyOn(_converse.connection, 'send');
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
expect(view.model.get('chat_state')).toBe('active');
const model = _converse.chatboxes.get(contact_jid);
expect(model.get('chat_state')).toBe('active');
expect(_converse.connection.send).toHaveBeenCalled();
const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
expect(stanza.getAttribute('to')).toBe(contact_jid);
@ -394,12 +396,12 @@ describe("Chatboxes", function () {
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
_converse.minimize.minimize(view.model);
expect(view.model.get('chat_state')).toBe('inactive');
const model = _converse.chatboxes.get(contact_jid);
_converse.minimize.minimize(model);
expect(model.get('chat_state')).toBe('inactive');
spyOn(_converse.connection, 'send');
_converse.minimize.maximize(view.model);
await u.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
_converse.minimize.maximize(model);
await u.waitUntil(() => model.get('chat_state') === 'active', 1000);
expect(_converse.connection.send).toHaveBeenCalled();
const calls = _.filter(_converse.connection.send.calls.all(), function (call) {
return call.args[0] instanceof Strophe.Builder;
@ -427,7 +429,7 @@ describe("Chatboxes", function () {
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
expect(view.model.get('chat_state')).toBe('active');
spyOn(_converse.connection, 'send');
spyOn(_converse.api, "trigger").and.callThrough();
@ -468,7 +470,7 @@ describe("Chatboxes", function () {
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => rosterview.querySelectorAll('.roster-group').length);
await mock.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
expect(view.model.get('chat_state')).toBe('active');
spyOn(_converse.connection, 'send');
spyOn(_converse.api, "trigger").and.callThrough();
@ -1162,7 +1164,7 @@ describe("Chatboxes", function () {
_converse.handleMessageStanza(msgFactory());
await u.waitUntil(() => chatbox.messages.length > 1);
expect(select_msgs_indicator().textContent).toBe('2');
_converse.minimize.minimize(view.model);
_converse.minimize.maximize(view.model);
u.waitUntil(() => typeof select_msgs_indicator() === 'undefined');
done();
}));

View File

@ -227,7 +227,7 @@ describe("The 'Add Contact' widget", function () {
mock.initConverse([], {'autocomplete_add_contact': false}, async function (done, _converse) {
await mock.waitForRoster(_converse, 'all', 0);
mock.openControlBox(_converse);
await mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.querySelector('.add-contact').click()
const modal = _converse.api.modal.get('add-contact-modal');
@ -274,6 +274,7 @@ describe("The 'Add Contact' widget", function () {
const XMLHttpRequestBackup = window.XMLHttpRequest;
window.XMLHttpRequest = MockXHR;
await mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.querySelector('.add-contact').click()
const modal = _converse.api.modal.get('add-contact-modal');

View File

@ -70,7 +70,7 @@ describe("A chat message", function () {
});
describe("A Groupcaht", function () {
describe("A Groupchat", function () {
it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'",
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
@ -117,7 +117,7 @@ describe("A Chatbox", function () {
expect(_converse.api.trigger.calls.count(), 2);
expect(u.isVisible(chatview)).toBeFalsy();
expect(chatview.model.get('minimized')).toBeTruthy();
chatview.querySelector('.toggle-chatbox-button').click();
document.querySelector('converse-minimized-chat').click();
await u.waitUntil(() => _converse.chatboxviews.keys().length);
const minimized_chats = document.querySelector("converse-minimized-chat")
@ -135,8 +135,7 @@ describe("A Chatbox", function () {
expect(u.isVisible(minimized_chats.firstElementChild)).toBe(false);
await _converse.api.chats.create(sender_jid, {'minimized': true});
await u.waitUntil(() => _converse.chatboxes.length > 1);
const view = _converse.chatboxviews.get(sender_jid);
expect(u.isVisible(view)).toBeFalsy();
expect(_converse.chatboxviews.get(sender_jid)).toBe(undefined);
expect(u.isVisible(minimized_chats.firstElementChild)).toBe(true);
expect(minimized_chats.firstElementChild.querySelectorAll('converse-minimized-chat').length).toBe(1);
expect(_converse.chatboxes.filter('minimized').length).toBe(1);
@ -149,7 +148,6 @@ describe("A Chatbox", function () {
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
let jid, chatboxview;
// openControlBox was called earlier, so the controlbox is
// visible, but no other chat boxes have been created.
expect(_converse.chatboxes.length).toEqual(1);
@ -170,12 +168,11 @@ describe("A Chatbox", function () {
for (i=0; i<online_contacts.length; i++) {
const el = online_contacts[i];
jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@montague.lit';
chatboxview = _converse.chatboxviews.get(jid);
chatboxview.model.set({'minimized': true});
const jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@montague.lit';
const model = _converse.chatboxes.get(jid);
model.set({'minimized': true});
}
await u.waitUntil(() => _converse.chatboxviews.keys().length);
spyOn(_converse.minimize, 'maximize').and.callThrough();
await u.waitUntil(() => _converse.chatboxviews.keys().length === 1);
const minimized_chats = document.querySelector("converse-minimized-chats")
minimized_chats.querySelector("a.restore-chat").click();
expect(_converse.minimize.trimChats.calls.count()).toBe(17);

View File

@ -25,10 +25,10 @@ describe("MUC Mention Notfications", function () {
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
await muc_creation_promise;
const view = api.chatviews.get(muc_jid);
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
expect(view.model.get('hidden')).toBe(true);
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED);
const model = _converse.chatboxes.get(muc_jid);
await u.waitUntil(() => (model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
expect(model.get('hidden')).toBe(true);
await u.waitUntil(() => model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED);
const room_el = await u.waitUntil(() => document.querySelector("converse-rooms-list .available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();

View File

@ -267,6 +267,7 @@ describe("A groupchat shown in the groupchats list", function () {
await mock.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC');
expect(_converse.chatboxes.length).toBe(2);
await mock.openControlBox(_converse);
const controlbox = _converse.chatboxviews.get('controlbox');
const lview = controlbox.querySelector('converse-rooms-list');
await u.waitUntil(() => lview.querySelectorAll(".open-room").length);

View File

@ -133,9 +133,9 @@ describe("XEP-0437 Room Activity Indicators", function () {
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
await muc_creation_promise;
const view = api.chatviews.get(muc_jid);
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
expect(view.model.get('hidden')).toBe(true);
const model = _converse.chatboxes.get(muc_jid);
await u.waitUntil(() => (model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
expect(model.get('hidden')).toBe(true);
const getSentPresences = () => sent_stanzas.filter(s => s.nodeName === 'presence');
@ -156,8 +156,8 @@ describe("XEP-0437 Room Activity Indicators", function () {
`</presence>`
);
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED);
expect(view.model.get('has_activity')).toBe(false);
await u.waitUntil(() => model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED);
expect(model.get('has_activity')).toBe(false);
const room_el = await u.waitUntil(() => document.querySelector("converse-rooms-list .available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
@ -171,7 +171,7 @@ describe("XEP-0437 Room Activity Indicators", function () {
`);
_converse.connection._dataRecv(mock.createRequest(activity_stanza));
await u.waitUntil(() => view.model.get('has_activity'));
await u.waitUntil(() => model.get('has_activity'));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
done();
}));

View File

@ -322,7 +322,7 @@ describe("The Contacts Roster", function () {
_converse.roster.get(jid).presence.set('show', 'online');
jid = mock.cur_names[4].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'dnd');
mock.openControlBox(_converse);
await mock.openControlBox(_converse);
const rosterview = document.querySelector('converse-roster');
const button = rosterview.querySelector('span[data-type="state"]');
button.click();
@ -488,7 +488,7 @@ describe("The Contacts Roster", function () {
await mock.waitForRoster(_converse, 'current', 0);
const groups = ['Colleagues', 'friends'];
mock.openControlBox(_converse);
await mock.openControlBox(_converse);
for (let i=0; i<mock.cur_names.length; i++) {
_converse.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit',
@ -514,7 +514,7 @@ describe("The Contacts Roster", function () {
mock.initConverse([], {}, async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 0);
mock.openControlBox(_converse);
await mock.openControlBox(_converse);
let i=0, j=0;
const groups = {
@ -568,8 +568,8 @@ describe("The Contacts Roster", function () {
async function (done, _converse) {
await mock.waitForRoster(_converse, 'all', 0);
const rosterview = document.querySelector('converse-roster');
await mock.openControlBox(_converse);
const rosterview = document.querySelector('converse-roster');
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
subscription: 'none',
@ -1072,7 +1072,7 @@ describe("The Contacts Roster", function () {
async function (done, _converse) {
await mock.waitForRoster(_converse, "current", 0);
mock.openControlBox(_converse);
await mock.openControlBox(_converse);
let names = [];
const addName = function (item) {
if (!u.hasClass('request-actions', item)) {

View File

@ -2,6 +2,13 @@ import { html } from 'lit-html';
import { repeat } from 'lit-html/directives/repeat.js';
import { _converse, api } from '@converse/headless/core';
function shouldShowChat (c) {
const { CONTROLBOX_TYPE } = _converse;
return c.get('type') === CONTROLBOX_TYPE || !(c.get('hidden') || c.get('minimized'));
}
export default () => {
const { chatboxes, CONTROLBOX_TYPE, CHATROOMS_TYPE, HEADLINES_TYPE } = _converse;
const view_mode = api.settings.get('view_mode');
@ -9,7 +16,7 @@ export default () => {
const logged_out = !connection?.connected || !connection?.authenticated || connection?.disconnecting;
return html`
${view_mode === 'overlayed' ? html`<converse-minimized-chats></converse-minimized-chats>` : ''}
${repeat(chatboxes, m => m.get('jid'), m => {
${repeat(chatboxes.filter(shouldShowChat), m => m.get('jid'), m => {
if (m.get('type') === CONTROLBOX_TYPE) {
return html`
${view_mode === 'overlayed' ? html`<converse-controlbox-toggle class="${!m.get('closed') ? 'hidden' : ''}"></converse-controlbox-toggle>` : ''}
@ -20,15 +27,15 @@ export default () => {
`;
} else if (m.get('type') === CHATROOMS_TYPE) {
return html`
<converse-muc jid="${m.get('jid')}" class="chatbox chatroom ${(m.get('hidden') || m.get('minimized')) ? 'hidden' : ''}"></converse-muc>
<converse-muc jid="${m.get('jid')}" class="chatbox chatroom"></converse-muc>
`;
} else if (m.get('type') === HEADLINES_TYPE) {
return html`
<converse-headlines jid="${m.get('jid')}" class="chatbox headlines ${(m.get('hidden') || m.get('minimized')) ? 'hidden' : ''}"></converse-headlines>
<converse-headlines jid="${m.get('jid')}" class="chatbox headlines"></converse-headlines>
`;
} else {
return html`
<converse-chat jid="${m.get('jid')}" class="chatbox ${(m.get('hidden') || m.get('minimized')) ? 'hidden' : ''}"></converse-chat>
<converse-chat jid="${m.get('jid')}" class="chatbox"></converse-chat>
`;
}
})}

View File

@ -118,17 +118,17 @@ converse.plugins.add('converse-minimize', {
_converse.minimize.minimize = minimize;
_converse.minimize.maximize = maximize;
function onChatInitialized (model) {
model.on( 'change:minimized', () => onMinimizedChanged(model));
}
/************************ BEGIN Event Handlers ************************/
api.listen.on('chatBoxViewInitialized', view => _converse.minimize.trimChats(view));
api.listen.on('chatRoomViewInitialized', view => _converse.minimize.trimChats(view));
api.listen.on('chatBoxMaximized', view => _converse.minimize.trimChats(view));
api.listen.on('controlBoxOpened', view => _converse.minimize.trimChats(view));
api.listen.on('chatBoxViewInitialized', v => v.listenTo(v.model, 'change:minimized', () => onMinimizedChanged(v)));
api.listen.on('chatRoomViewInitialized', view => {
view.listenTo(view.model, 'change:minimized', () => onMinimizedChanged(view));
view.model.get('minimized') && view.hide();
});
api.listen.on('chatBoxInitialized', onChatInitialized);
api.listen.on('chatRoomInitialized', onChatInitialized);
api.listen.on('getHeadingButtons', (view, buttons) => {
if (view.model.get('type') === _converse.CHATROOMS_TYPE) {

View File

@ -145,13 +145,23 @@ export function maximize (ev, chatbox) {
});
}
export function minimize (ev, chatbox) {
export function minimize (ev, model) {
if (ev?.preventDefault) {
ev.preventDefault();
} else {
chatbox = ev;
model = ev;
}
u.safeSave(chatbox, {
// save the scroll position to restore it on maximize
const view = _converse.chatboxviews.get(model.get('jid'));
const content = view.querySelector('.chat-content__messages');
const scroll = content.scrollTop;
if (model.collection && model.collection.browserStorage) {
model.save({ scroll });
} else {
model.set({ scroll });
}
model.setChatState(_converse.INACTIVE);
u.safeSave(model, {
'hidden': true,
'minimized': true,
'time_minimized': new Date().toISOString()
@ -165,20 +175,18 @@ export function minimize (ev, chatbox) {
* Will trigger {@link _converse#chatBoxMaximized}
* @returns {_converse.ChatBoxView|_converse.ChatRoomView}
*/
function onMaximized (view) {
if (!view.model.isScrolledUp()) {
view.model.clearUnreadMsgCounter();
function onMaximized (model) {
if (!model.isScrolledUp()) {
model.clearUnreadMsgCounter();
}
view.model.setChatState(_converse.ACTIVE);
view.show();
model.setChatState(_converse.ACTIVE);
/**
* Triggered when a previously minimized chat gets maximized
* @event _converse#chatBoxMaximized
* @type { _converse.ChatBoxView }
* @example _converse.api.listen.on('chatBoxMaximized', view => { ... });
*/
api.trigger('chatBoxMaximized', view);
return view;
api.trigger('chatBoxMaximized', model);
}
/**
@ -188,29 +196,20 @@ function onMaximized (view) {
* Will trigger {@link _converse#chatBoxMinimized}
* @returns {_converse.ChatBoxView|_converse.ChatRoomView}
*/
function onMinimized (view) {
// save the scroll position to restore it on maximize
const content = view.querySelector('.chat-content__messages');
if (view.model.collection && view.model.collection.browserStorage) {
view.model.save({ 'scroll': content.scrollTop });
} else {
view.model.set({ 'scroll': content.scrollTop });
}
view.model.setChatState(_converse.INACTIVE);
function onMinimized (model) {
/**
* Triggered when a previously maximized chat gets Minimized
* @event _converse#chatBoxMinimized
* @type { _converse.ChatBoxView }
* @example _converse.api.listen.on('chatBoxMinimized', view => { ... });
*/
api.trigger('chatBoxMinimized', view);
return view;
api.trigger('chatBoxMinimized', model);
}
export function onMinimizedChanged (view) {
if (view.model.get('minimized')) {
onMinimized(view);
export function onMinimizedChanged (model) {
if (model.get('minimized')) {
onMinimized(model);
} else {
onMaximized(view);
onMaximized(model);
}
}

View File

@ -13,6 +13,12 @@ export default class BaseChatView extends ElementView {
this.debouncedScrollDown = debounce(this.scrollDown, 100);
}
disconnectedCallback () {
super.disconnectedCallback();
const jid = this.getAttribute('jid');
_converse.chatboxviews.remove(jid, this);
}
hideNewMessagesIndicator () {
const new_msgs_indicator = this.querySelector('.new-msgs-indicator');
if (new_msgs_indicator !== null) {
@ -195,7 +201,7 @@ export default class BaseChatView extends ElementView {
'scrollTop': null
});
}
this.querySelector('.chat-content__messages').scrollDown();
this.querySelector('.chat-content__messages')?.scrollDown();
this.onScrolledDown();
}