Add api for managing modals

Set passed-in properties on modal
This commit is contained in:
JC Brand 2020-12-02 18:37:32 +01:00
parent b18cc6bcc5
commit 1a161ad2c7
24 changed files with 168 additions and 136 deletions

View File

@ -24,6 +24,7 @@ module.exports = function(config) {
{ pattern: "node_modules/sinon/pkg/sinon.js", type: 'module' }, { pattern: "node_modules/sinon/pkg/sinon.js", type: 'module' },
{ pattern: "spec/mock.js", type: 'module' }, { pattern: "spec/mock.js", type: 'module' },
{ pattern: "spec/user-details-modal.js", type: 'module' },
{ pattern: "spec/spoilers.js", type: 'module' }, { pattern: "spec/spoilers.js", type: 'module' },
{ pattern: "spec/emojis.js", type: 'module' }, { pattern: "spec/emojis.js", type: 'module' },
{ pattern: "spec/muclist.js", type: 'module' }, { pattern: "spec/muclist.js", type: 'module' },
@ -44,7 +45,6 @@ module.exports = function(config) {
{ pattern: "spec/controlbox.js", type: 'module' }, { pattern: "spec/controlbox.js", type: 'module' },
{ pattern: "spec/roster.js", type: 'module' }, { pattern: "spec/roster.js", type: 'module' },
{ pattern: "spec/chatbox.js", type: 'module' }, { pattern: "spec/chatbox.js", type: 'module' },
{ pattern: "spec/user-details-modal.js", type: 'module' },
{ pattern: "spec/messages.js", type: 'module' }, { pattern: "spec/messages.js", type: 'module' },
{ pattern: "spec/corrections.js", type: 'module' }, { pattern: "spec/corrections.js", type: 'module' },
{ pattern: "spec/styling.js", type: 'module' }, { pattern: "spec/styling.js", type: 'module' },

View File

@ -154,8 +154,7 @@ describe("The Controlbox", function () {
await mock.openControlBox(_converse); await mock.openControlBox(_converse);
var cbview = _converse.chatboxviews.get('controlbox'); var cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click() cbview.el.querySelector('.change-status').click()
var modal = _converse.xmppstatusview.status_modal; const modal = _converse.api.modal.get('modal-status-change');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
const view = _converse.xmppstatusview; const view = _converse.xmppstatusview;
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd" modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
@ -183,7 +182,7 @@ describe("The Controlbox", function () {
await mock.openControlBox(_converse); await mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click() cbview.el.querySelector('.change-status').click()
const modal = _converse.xmppstatusview.status_modal; const modal = _converse.api.modal.get('modal-status-change');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
const view = _converse.xmppstatusview; const view = _converse.xmppstatusview;
@ -219,7 +218,7 @@ describe("The 'Add Contact' widget", function () {
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click() cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal; const modal = _converse.api.modal.get('add-contact-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null); expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null);
@ -252,7 +251,7 @@ describe("The 'Add Contact' widget", function () {
mock.openControlBox(_converse); mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click() cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal; const modal = _converse.api.modal.get('add-contact-modal');
expect(modal.jid_auto_complete).toBe(undefined); expect(modal.jid_auto_complete).toBe(undefined);
expect(modal.name_auto_complete).toBe(undefined); expect(modal.name_auto_complete).toBe(undefined);
@ -299,7 +298,7 @@ describe("The 'Add Contact' widget", function () {
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click() cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal; const modal = _converse.api.modal.get('add-contact-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
// We only have autocomplete for the name input // We only have autocomplete for the name input
@ -369,7 +368,7 @@ describe("The 'Add Contact' widget", function () {
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click() cbview.el.querySelector('.add-contact').click()
modal = _converse.rosterview.add_contact_modal; modal = _converse.api.modal.get('add-contact-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.jid_auto_complete).toBe(undefined); expect(modal.jid_auto_complete).toBe(undefined);

View File

@ -152,7 +152,7 @@ window.addEventListener('converse-loaded', () => {
const roomspanel = view.roomspanel; const roomspanel = view.roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
mock.closeControlBox(_converse); mock.closeControlBox(_converse);
const modal = roomspanel.add_room_modal; const modal = _converse.api.modal.get('add-chatroom-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1500) await u.waitUntil(() => u.isVisible(modal.el), 1500)
modal.el.querySelector('input[name="chatroom"]').value = jid; modal.el.querySelector('input[name="chatroom"]').value = jid;
if (nick) { if (nick) {

View File

@ -1,4 +1,4 @@
/*global mock */ /*global mock, converse */
const _ = converse.env._; const _ = converse.env._;
const $iq = converse.env.$iq; const $iq = converse.env.$iq;
@ -8,13 +8,13 @@ const Strophe = converse.env.Strophe;
const u = converse.env.utils; const u = converse.env.utils;
async function openModtools (view) { async function openModtools (_converse, view) {
const textarea = view.el.querySelector('.chat-textarea'); const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools'; textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 }; const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter); view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count()); await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal; const modal = _converse.api.modal.get('converse-modtools-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
return modal; return modal;
} }
@ -40,7 +40,7 @@ describe("The groupchat moderator tool", function () {
const view = _converse.chatboxviews.get(muc_jid); const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 5), 1000); await u.waitUntil(() => (view.model.occupants.length === 5), 1000);
const modal = await openModtools(view); const modal = await openModtools(_converse, view);
let tab = modal.el.querySelector('#affiliations-tab'); let tab = modal.el.querySelector('#affiliations-tab');
// Clear so that we don't match older stanzas // Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = []; _converse.connection.IQ_stanzas = [];
@ -163,7 +163,7 @@ describe("The groupchat moderator tool", function () {
// Clear so that we don't match older stanzas // Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = []; _converse.connection.IQ_stanzas = [];
const modal = await openModtools(view); const modal = await openModtools(_converse, view);
const select = modal.el.querySelector('.select-affiliation'); const select = modal.el.querySelector('.select-affiliation');
expect(select.value).toBe('owner'); expect(select.value).toBe('owner');
select.value = 'member'; select.value = 'member';
@ -270,7 +270,7 @@ describe("The groupchat moderator tool", function () {
view.onKeyDown(enter); view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count()); await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal; const modal = _converse.api.modal.get('converse-modtools-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
const tab = modal.el.querySelector('#roles-tab'); const tab = modal.el.querySelector('#roles-tab');
@ -326,7 +326,7 @@ describe("The groupchat moderator tool", function () {
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members); await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid); const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 5)); await u.waitUntil(() => (view.model.occupants.length === 5));
const modal = await openModtools(view); const modal = await openModtools(_converse, view);
const tab = modal.el.querySelector('#affiliations-tab'); const tab = modal.el.querySelector('#affiliations-tab');
// Clear so that we don't match older stanzas // Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = []; _converse.connection.IQ_stanzas = [];
@ -375,7 +375,7 @@ describe("The groupchat moderator tool", function () {
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members); await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid); const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 2)); await u.waitUntil(() => (view.model.occupants.length === 2));
const modal = await openModtools(view); const modal = await openModtools(_converse, view);
// Clear so that we don't match older stanzas // Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = []; _converse.connection.IQ_stanzas = [];
@ -443,7 +443,7 @@ describe("The groupchat moderator tool", function () {
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members); await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid); const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 3)); await u.waitUntil(() => (view.model.occupants.length === 3));
const modal = await openModtools(view); const modal = await openModtools(_converse, view);
const tab = modal.el.querySelector('#affiliations-tab'); const tab = modal.el.querySelector('#affiliations-tab');
// Clear so that we don't match older stanzas // Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = []; _converse.connection.IQ_stanzas = [];
@ -481,7 +481,7 @@ describe("The groupchat moderator tool", function () {
view.onKeyDown(enter); view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count()); await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal; const modal = _converse.api.modal.get('converse-modtools-modal');
const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid}); const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid});
expect(modal.getAssignableAffiliations(occupant)).toEqual(['owner', 'admin', 'member', 'outcast', 'none']); expect(modal.getAssignableAffiliations(occupant)).toEqual(['owner', 'admin', 'member', 'outcast', 'none']);

View File

@ -1973,7 +1973,7 @@ describe("Groupchats", function () {
await u.waitUntil(() => view.el.querySelector('.open-invite-modal')); await u.waitUntil(() => view.el.querySelector('.open-invite-modal'));
view.el.querySelector('.open-invite-modal').click(); view.el.querySelector('.open-invite-modal').click();
const modal = view.muc_invite_modal; const modal = _converse.api.modal.get('muc-invite-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000) await u.waitUntil(() => u.isVisible(modal.el), 1000)
expect(modal.el.querySelectorAll('#invitee_jids').length).toBe(1); expect(modal.el.querySelectorAll('#invitee_jids').length).toBe(1);
@ -2456,7 +2456,7 @@ describe("Groupchats", function () {
const info_el = view.el.querySelector(".show-room-details-modal"); const info_el = view.el.querySelector(".show-room-details-modal");
info_el.click(); info_el.click();
const modal = view.model.room_details_modal; const modal = _converse.api.modal.get('room-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
let features_list = modal.el.querySelector('.features-list'); let features_list = modal.el.querySelector('.features-list');
@ -4629,7 +4629,7 @@ describe("Groupchats", function () {
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
mock.closeControlBox(_converse); mock.closeControlBox(_converse);
const modal = roomspanel.add_room_modal; const modal = _converse.api.modal.get('add-chatroom-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000) await u.waitUntil(() => u.isVisible(modal.el), 1000)
let label_name = modal.el.querySelector('label[for="chatroom"]'); let label_name = modal.el.querySelector('label[for="chatroom"]');
@ -4670,7 +4670,7 @@ describe("Groupchats", function () {
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
mock.closeControlBox(_converse); mock.closeControlBox(_converse);
const modal = roomspanel.add_room_modal; const modal = _converse.api.modal.get('add-chatroom-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000) await u.waitUntil(() => u.isVisible(modal.el), 1000)
const name_input = modal.el.querySelector('input[name="chatroom"]'); const name_input = modal.el.querySelector('input[name="chatroom"]');
name_input.value = 'lounge@montague.lit'; name_input.value = 'lounge@montague.lit';
@ -4693,7 +4693,7 @@ describe("Groupchats", function () {
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
mock.closeControlBox(_converse); mock.closeControlBox(_converse);
const modal = roomspanel.add_room_modal; const modal = _converse.api.modal.get('add-chatroom-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000) await u.waitUntil(() => u.isVisible(modal.el), 1000)
const label_nick = modal.el.querySelector('label[for="nickname"]'); const label_nick = modal.el.querySelector('label[for="nickname"]');
expect(label_nick.textContent.trim()).toBe('Nickname:'); expect(label_nick.textContent.trim()).toBe('Nickname:');
@ -4712,7 +4712,7 @@ describe("Groupchats", function () {
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
mock.closeControlBox(_converse); mock.closeControlBox(_converse);
const modal = roomspanel.add_room_modal; const modal = _converse.api.modal.get('add-chatroom-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000) await u.waitUntil(() => u.isVisible(modal.el), 1000)
const label_nick = modal.el.querySelector('label[for="nickname"]'); const label_nick = modal.el.querySelector('label[for="nickname"]');
expect(label_nick.textContent.trim()).toBe('Nickname:'); expect(label_nick.textContent.trim()).toBe('Nickname:');
@ -4729,7 +4729,7 @@ describe("Groupchats", function () {
await mock.openControlBox(_converse); await mock.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
const modal = roomspanel.add_room_modal; const modal = _converse.api.modal.get('add-chatroom-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000) await u.waitUntil(() => u.isVisible(modal.el), 1000)
expect(modal.el.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat'); expect(modal.el.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat');
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve()); spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
@ -4769,7 +4769,7 @@ describe("Groupchats", function () {
await mock.openControlBox(_converse); await mock.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
const modal = roomspanel.add_room_modal; const modal = _converse.api.modal.get('add-chatroom-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000) await u.waitUntil(() => u.isVisible(modal.el), 1000)
expect(modal.el.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat'); expect(modal.el.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat');
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve()); spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
@ -4812,7 +4812,7 @@ describe("Groupchats", function () {
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-list-muc-modal').click(); roomspanel.el.querySelector('.show-list-muc-modal').click();
mock.closeControlBox(_converse); mock.closeControlBox(_converse);
const modal = roomspanel.muc_list_modal; const modal = _converse.api.modal.get('list-chatrooms-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve()); spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
@ -4889,7 +4889,7 @@ describe("Groupchats", function () {
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-list-muc-modal').click(); roomspanel.el.querySelector('.show-list-muc-modal').click();
mock.closeControlBox(_converse); mock.closeControlBox(_converse);
const modal = roomspanel.muc_list_modal; const modal = _converse.api.modal.get('list-chatrooms-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
const server_input = modal.el.querySelector('input[name="server"]'); const server_input = modal.el.querySelector('input[name="server"]');
expect(server_input.value).toBe('muc.example.org'); expect(server_input.value).toBe('muc.example.org');
@ -4906,7 +4906,7 @@ describe("Groupchats", function () {
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-list-muc-modal').click(); roomspanel.el.querySelector('.show-list-muc-modal').click();
mock.closeControlBox(_converse); mock.closeControlBox(_converse);
const modal = roomspanel.muc_list_modal; const modal = _converse.api.modal.get('list-chatrooms-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve()); spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called

View File

@ -204,7 +204,7 @@ describe("A groupchat shown in the groupchats list", function () {
const info_el = _converse.rooms_list_view.el.querySelector(".room-info"); const info_el = _converse.rooms_list_view.el.querySelector(".room-info");
info_el.click(); info_el.click();
const modal = view.model.room_details_modal; const modal = _converse.api.modal.get('room-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
let els = modal.el.querySelectorAll('p.room-info'); let els = modal.el.querySelectorAll('p.room-info');
expect(els[0].textContent).toBe("Name: A Dark Cave") expect(els[0].textContent).toBe("Name: A Dark Cave")

View File

@ -1463,7 +1463,7 @@ describe("The OMEMO module", function() {
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
const show_modal_button = view.el.querySelector('.show-user-details-modal'); const show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click(); show_modal_button.click();
const modal = view.user_details_modal; const modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid)); let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
expect(Strophe.serialize(iq_stanza)).toBe( expect(Strophe.serialize(iq_stanza)).toBe(

View File

@ -1,4 +1,5 @@
/*global mock */ /*global mock, converse */
// See: https://xmpp.org/rfcs/rfc3921.html // See: https://xmpp.org/rfcs/rfc3921.html
const original_timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; const original_timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
@ -76,7 +77,7 @@ describe("A sent presence stanza", function () {
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
const change_status_el = await u.waitUntil(() => cbview.el.querySelector('.change-status')); const change_status_el = await u.waitUntil(() => cbview.el.querySelector('.change-status'));
change_status_el.click() change_status_el.click()
const modal = _converse.xmppstatusview.status_modal; let modal = _converse.api.modal.get('modal-status-change');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
const msg = 'My custom status'; const msg = 'My custom status';
modal.el.querySelector('input[name="status_message"]').value = msg; modal.el.querySelector('input[name="status_message"]').value = msg;
@ -90,10 +91,11 @@ describe("A sent presence stanza", function () {
`<priority>0</priority>`+ `<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="PxXfr6uz8ClMWIga0OB/MhKNH/M=" xmlns="http://jabber.org/protocol/caps"/>`+ `<c hash="sha-1" node="https://conversejs.org" ver="PxXfr6uz8ClMWIga0OB/MhKNH/M=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`) `</presence>`)
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true"); await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true");
await u.waitUntil(() => !u.isVisible(modal.el)); await u.waitUntil(() => !u.isVisible(modal.el));
cbview.el.querySelector('.change-status').click() cbview.el.querySelector('.change-status').click()
modal = _converse.api.modal.get('modal-status-change');
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000); await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000);
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd" modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
modal.el.querySelector('[type="submit"]').click(); modal.el.querySelector('[type="submit"]').click();

View File

@ -66,7 +66,7 @@ describe("The Protocol", function () {
}); });
cbview.el.querySelector('.add-contact').click() cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal; const modal = _converse.api.modal.get('add-contact-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
spyOn(modal, "addContactFromForm").and.callThrough(); spyOn(modal, "addContactFromForm").and.callThrough();
modal.delegateEvents(); modal.delegateEvents();

View File

@ -18,7 +18,7 @@ describe("The User Details Modal", function () {
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal'); let show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click(); show_modal_button.click();
const modal = view.user_details_modal; const modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
spyOn(window, 'confirm').and.returnValue(true); spyOn(window, 'confirm').and.returnValue(true);
spyOn(view.model.contact, 'removeFromRoster').and.callFake(callback => callback()); spyOn(view.model.contact, 'removeFromRoster').and.callFake(callback => callback());
@ -45,7 +45,7 @@ describe("The User Details Modal", function () {
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal'); let show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click(); show_modal_button.click();
const modal = view.user_details_modal; let modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 2000); await u.waitUntil(() => u.isVisible(modal.el), 2000);
spyOn(window, 'confirm').and.returnValue(true); spyOn(window, 'confirm').and.returnValue(true);
@ -53,7 +53,7 @@ describe("The User Details Modal", function () {
let remove_contact_button = modal.el.querySelector('button.remove-contact'); let remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy(); expect(u.isVisible(remove_contact_button)).toBeTruthy();
remove_contact_button.click(); remove_contact_button.click();
await u.waitUntil(() => !u.isVisible(modal.el))
await u.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000); await u.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
const header = document.querySelector('.alert-danger .modal-title'); const header = document.querySelector('.alert-danger .modal-title');
@ -63,6 +63,7 @@ describe("The User Details Modal", function () {
document.querySelector('.alert-danger button.close').click(); document.querySelector('.alert-danger button.close').click();
show_modal_button = view.el.querySelector('.show-user-details-modal'); show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click(); show_modal_button.click();
modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 2000) await u.waitUntil(() => u.isVisible(modal.el), 2000)
show_modal_button = view.el.querySelector('.show-user-details-modal'); show_modal_button = view.el.querySelector('.show-user-details-modal');

View File

@ -15,14 +15,9 @@ export default class MessageBody extends CustomElement {
} }
} }
showImageModal (ev) { showImageModal (ev) { // eslint-disable-line class-methods-use-this
ev.preventDefault(); ev.preventDefault();
if (this.image_modal === undefined) { api.modal.create(ImageModal, {'src': ev.target.src}, ev).show(ev);
this.image_modal = new ImageModal();
}
this.image_modal.src = ev.target.src;
this.image_modal.render();
this.image_modal.show(ev);
} }
render () { render () {

View File

@ -238,10 +238,7 @@ export const ChatBoxView = View.extend({
showUserDetailsModal (ev) { showUserDetailsModal (ev) {
ev.preventDefault(); ev.preventDefault();
if (this.user_details_modal === undefined) { api.modal.show(UserDetailsModal, {model: this.model}, ev);
this.user_details_modal = new UserDetailsModal({model: this.model});
}
this.user_details_modal.show(ev);
}, },
onDragOver (evt) { onDragOver (evt) {

View File

@ -7,15 +7,77 @@ import Alert from './modals/alert.js';
import BootstrapModal from './modals/base.js'; import BootstrapModal from './modals/base.js';
import Confirm from './modals/confirm.js'; import Confirm from './modals/confirm.js';
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
import { _converse, converse } from "@converse/headless/converse-core"; import { _converse, api, converse } from "@converse/headless/converse-core";
converse.env.BootstrapModal = BootstrapModal; // expose to plugins converse.env.BootstrapModal = BootstrapModal; // expose to plugins
let alert; let modals = [];
const modal_api = { const modal_api = {
/**
* API namespace for methods relating to modals
* @namespace _converse.api.modal
* @memberOf _converse.api
*/
modal: {
/**
* Shows a modal of type `ModalClass` to the user.
* Will create a new instance of that class if an existing one isn't
* found.
* @param { Class } ModalClass
* @param { [Object] } properties - Optional properties that will be
* set on a newly created modal instance (if no pre-existing modal was
* found).
* @param { [Event] } event - The DOM event that causes the modal to be shown.
*/
show (ModalClass, properties, ev) {
const modal = this.get(ModalClass.id) || this.create(ModalClass, properties);
modal.show(ev);
return modal;
},
/**
* Return a modal with the passed-in identifier, if it exists.
* @param { String } id
*/
get (id) {
return modals.filter(m => m.id == id).pop();
},
/**
* Create a modal of the passed-in type.
* @param { Class } ModalClass
* @param { [Object] } properties - Optional properties that will be
* set on the modal instance.
*/
create (ModalClass, properties) {
const modal = new ModalClass(properties);
modals.push(modal);
return modal;
},
/**
* Remove a particular modal
* @param { View } modal
*/
remove (modal) {
modals = modals.filter(m => m !== modal);
modal.remove();
},
/**
* Remove all modals
*/
removeAll () {
modals.forEach(m => m.remove());
modals = [];
}
},
/** /**
* Show a confirm modal to the user. * Show a confirm modal to the user.
* @method _converse.api.confirm * @method _converse.api.confirm
@ -101,22 +163,13 @@ const modal_api = {
level = 'alert-warning'; level = 'alert-warning';
} }
if (alert === undefined) { const model = new Model({
const model = new Model({ 'title': title,
'title': title, 'messages': messages,
'messages': messages, 'level': level,
'level': level, 'type': 'alert'
'type': 'alert' })
}) api.modal.show(Alert, {model});
alert = new Alert({model});
} else {
alert.model.set({
'title': title,
'messages': messages,
'level': level
});
}
alert.show();
} }
} }
@ -124,12 +177,15 @@ const modal_api = {
converse.plugins.add('converse-modal', { converse.plugins.add('converse-modal', {
initialize () { initialize () {
_converse.api.listen.on('disconnect', () => { api.listen.on('disconnect', () => {
const container = document.querySelector("#converse-modals"); const container = document.querySelector("#converse-modals");
if (container) { if (container) {
container.innerHTML = ''; container.innerHTML = '';
} }
}); });
api.listen.on('clearSession', () => api.modal.removeAll());
Object.assign(_converse.api, modal_api); Object.assign(_converse.api, modal_api);
} }
}); });

View File

@ -501,29 +501,24 @@ export const ChatRoomView = ChatBoxView.extend({
if (!this.verifyRoles(['moderator'])) { if (!this.verifyRoles(['moderator'])) {
return; return;
} }
if (typeof this.model.modtools_modal === 'undefined') { let modal = api.modal.get(ModeratorToolsModal.id);
const model = new Model({'affiliation': affiliation}); if (modal) {
this.modtools_modal = new ModeratorToolsModal({model, _converse, 'chatroomview': this}); modal.model.set('affiliation', affiliation);
} else { } else {
this.modtools_modal.set('affiliation', affiliation); const model = new Model({'affiliation': affiliation});
modal = api.modal.create(ModeratorToolsModal, {model, _converse, 'chatroomview': this});
} }
this.modtools_modal.show(); modal.show();
}, },
showRoomDetailsModal (ev) { showRoomDetailsModal (ev) {
ev.preventDefault(); ev.preventDefault();
if (this.model.room_details_modal === undefined) { api.modal.show(RoomDetailsModal, {'model': this.model}, ev);
this.model.room_details_modal = new RoomDetailsModal({'model': this.model});
}
this.model.room_details_modal.show(ev);
}, },
showOccupantDetailsModal (ev, message) { showOccupantDetailsModal (ev, message) {
ev.preventDefault(); ev.preventDefault();
if (this.model.occupant_modal === undefined) { api.modal.show(OccupantModal, {'model': message.occupant}, ev);
this.model.occupant_modal = new OccupantModal({'model': message.occupant});
}
this.model.occupant_modal.show(ev);
}, },
showChatStateNotification (message) { showChatStateNotification (message) {
@ -679,12 +674,7 @@ export const ChatRoomView = ChatBoxView.extend({
showInviteModal (ev) { showInviteModal (ev) {
ev.preventDefault(); ev.preventDefault();
if (this.muc_invite_modal === undefined) { api.modal.show(MUCInviteModal, {'model': new Model(), 'chatroomview': this}, ev);
this.muc_invite_modal = new MUCInviteModal({'model': new Model()});
// TODO: remove once we have API for sending direct invite
this.muc_invite_modal.chatroomview = this;
}
this.muc_invite_modal.show(ev);
}, },
@ -1365,17 +1355,11 @@ export const RoomsPanel = View.extend({
}, },
showAddRoomModal (ev) { showAddRoomModal (ev) {
if (this.add_room_modal === undefined) { api.modal.show(AddMUCModal, {'model': this.model}, ev);
this.add_room_modal = new AddMUCModal({'model': this.model});
}
this.add_room_modal.show(ev);
}, },
showMUCListModal(ev) { showMUCListModal(ev) {
if (this.muc_list_modal === undefined) { api.modal.show(MUCListModal, {'model': this.model}, ev);
this.muc_list_modal = new MUCListModal({'model': this.model});
}
this.muc_list_modal.show(ev);
} }
}); });

View File

@ -61,26 +61,17 @@ converse.plugins.add('converse-profile', {
showProfileModal (ev) { showProfileModal (ev) {
ev.preventDefault(); ev.preventDefault();
if (this.profile_modal === undefined) { api.modal.show(_converse.ProfileModal, {model: this.model}, ev);
this.profile_modal = new _converse.ProfileModal({model: this.model});
}
this.profile_modal.show(ev);
}, },
showStatusChangeModal (ev) { showStatusChangeModal (ev) {
ev.preventDefault(); ev.preventDefault();
if (this.status_modal === undefined) { api.modal.show(_converse.ChatStatusModal, {model: this.model}, ev);
this.status_modal = new _converse.ChatStatusModal({model: this.model});
}
this.status_modal.show(ev);
}, },
showUserSettingsModal(ev) { showUserSettingsModal(ev) {
ev.preventDefault(); ev.preventDefault();
if (this.user_settings_modal === undefined) { api.modal.show(UserSettingsModal, {model: this.model, _converse}, ev);
this.user_settings_modal = new UserSettingsModal({model: this.model, _converse});
}
this.user_settings_modal.show(ev);
}, },
logOut (ev) { logOut (ev) {

View File

@ -109,10 +109,7 @@ converse.plugins.add('converse-roomslist', {
const jid = ev.target.getAttribute('data-room-jid'); const jid = ev.target.getAttribute('data-room-jid');
const room = _converse.chatboxes.get(jid); const room = _converse.chatboxes.get(jid);
ev.preventDefault(); ev.preventDefault();
if (room.room_details_modal === undefined) { api.modal.show(RoomDetailsModal, {'model': room}, ev);
room.room_details_modal = new RoomDetailsModal({'model': room});
}
room.room_details_modal.show(ev);
}, },
async openRoom (ev) { async openRoom (ev) {
@ -183,4 +180,3 @@ converse.plugins.add('converse-roomslist', {
api.listen.on('reconnected', initRoomsListView); api.listen.on('reconnected', initRoomsListView);
} }
}); });

View File

@ -629,10 +629,7 @@ converse.plugins.add('converse-rosterview', {
}, },
showAddContactModal (ev) { showAddContactModal (ev) {
if (this.add_contact_modal === undefined) { api.modal.show(_converse.AddContactModal, {'model': new Model()}, ev);
this.add_contact_modal = new _converse.AddContactModal({'model': new Model()});
}
this.add_contact_modal.show(ev);
}, },
createRosterFilter () { createRosterFilter () {

View File

@ -4,6 +4,7 @@ import { __ } from '../i18n';
const Alert = BootstrapModal.extend({ const Alert = BootstrapModal.extend({
id: 'alert-modal',
initialize () { initialize () {
BootstrapModal.prototype.initialize.apply(this, arguments); BootstrapModal.prototype.initialize.apply(this, arguments);

View File

@ -2,7 +2,7 @@ import bootstrap from "bootstrap.native";
import log from "@converse/headless/log"; import log from "@converse/headless/log";
import tpl_alert_component from "templates/alert.js"; import tpl_alert_component from "templates/alert.js";
import { View } from '@converse/skeletor/src/view.js'; import { View } from '@converse/skeletor/src/view.js';
import { _converse, converse } from "@converse/headless/converse-core"; import { _converse, api, converse } from "@converse/headless/converse-core";
import { render } from 'lit-html'; import { render } from 'lit-html';
const { sizzle } = converse.env; const { sizzle } = converse.env;
@ -16,9 +16,15 @@ const BaseModal = View.extend({
'click .nav-item .nav-link': 'switchTab' 'click .nav-item .nav-link': 'switchTab'
}, },
initialize () { initialize (options) {
if (!this.id) {
throw new Error("Each modal class must have a unique id attribute");
}
this.render() this.render()
// Allow properties to be set via passed in options
Object.assign(this, options);
this.el.setAttribute('tabindex', '-1'); this.el.setAttribute('tabindex', '-1');
this.el.setAttribute('role', 'dialog'); this.el.setAttribute('role', 'dialog');
this.el.setAttribute('aria-hidden', 'true'); this.el.setAttribute('aria-hidden', 'true');
@ -36,7 +42,7 @@ const BaseModal = View.extend({
onHide () { onHide () {
u.removeClass('selected', this.trigger_el); u.removeClass('selected', this.trigger_el);
!this.persistent && this.remove(); !this.persistent && api.modal.remove(this);
}, },
insertIntoDOM () { insertIntoDOM () {

View File

@ -6,6 +6,7 @@ const u = converse.env.utils;
const Confirm = BootstrapModal.extend({ const Confirm = BootstrapModal.extend({
id: 'confirm-modal',
events: { events: {
'submit .confirm': 'onConfimation' 'submit .confirm': 'onConfimation'
}, },

View File

@ -3,6 +3,8 @@ import tpl_image_modal from "./templates/image.js";
export default BootstrapModal.extend({ export default BootstrapModal.extend({
id: 'image-modal',
toHTML () { toHTML () {
return tpl_image_modal({ return tpl_image_modal({
'src': this.src, 'src': this.src,

View File

@ -3,7 +3,7 @@ import tpl_message_versions_modal from "./templates/message-versions.js";
export default BootstrapModal.extend({ export default BootstrapModal.extend({
id: "message-versions-modal",
toHTML () { toHTML () {
return tpl_message_versions_modal(this.model.toJSON()); return tpl_message_versions_modal(this.model.toJSON());
} }

View File

@ -12,6 +12,7 @@ let _converse;
export default BootstrapModal.extend({ export default BootstrapModal.extend({
id: "converse-modtools-modal",
persistent: true, persistent: true,
initialize (attrs) { initialize (attrs) {

View File

@ -7,7 +7,22 @@ import { _converse, api, converse } from "@converse/headless/converse-core";
const u = converse.env.utils; const u = converse.env.utils;
function removeContact (contact) {
contact.removeFromRoster(
() => contact.destroy(),
(e) => {
e && log.error(e);
api.alert('error', __('Error'), [
__('Sorry, there was an error while trying to remove %1$s as a contact.',
contact.getDisplayName())
]);
}
);
}
const UserDetailsModal = BootstrapModal.extend({ const UserDetailsModal = BootstrapModal.extend({
id: 'user-details-modal',
persistent: true, persistent: true,
events: { events: {
@ -74,23 +89,11 @@ const UserDetailsModal = BootstrapModal.extend({
if (!api.settings.get('allow_contact_removal')) { return; } if (!api.settings.get('allow_contact_removal')) { return; }
const result = confirm(__("Are you sure you want to remove this contact?")); const result = confirm(__("Are you sure you want to remove this contact?"));
if (result === true) { if (result === true) {
this.modal.hide(); // XXX: The `dismissHandler` in bootstrap.native tries to
// XXX: This is annoying but necessary to get tests to pass.
// The `dismissHandler` in bootstrap.native tries to
// reference the remove button after it's been cleared from // reference the remove button after it's been cleared from
// the DOM, so we delay removing the contact to give it time. // the DOM, so we delay removing the contact to give it time.
setTimeout(() => { setTimeout(() => removeContact(this.model.contact), 1);
this.model.contact.removeFromRoster( this.modal.hide();
() => this.model.contact.destroy(),
(err) => {
log.error(err);
api.alert('error', __('Error'), [
__('Sorry, there was an error while trying to remove %1$s as a contact.',
this.model.contact.getDisplayName())
]);
}
);
}, 1);
} }
}, },
}); });