Create an ElementView base modal and use it for all modals
Modals are now all web components and are opened by component name.
This commit is contained in:
parent
927add0707
commit
fbe86e5af8
@ -106,6 +106,7 @@ module.exports = function(config) {
|
||||
{ pattern: "src/plugins/register/tests/register.js", type: 'module' },
|
||||
{ pattern: "src/plugins/roomslist/tests/roomslist.js", type: 'module' },
|
||||
{ pattern: "src/plugins/rootview/tests/root.js", type: 'module' },
|
||||
{ pattern: "src/plugins/rosterview/tests/add-contact-modal.js", type: 'module' },
|
||||
{ pattern: "src/plugins/rosterview/tests/presence.js", type: 'module' },
|
||||
{ pattern: "src/plugins/rosterview/tests/protocol.js", type: 'module' },
|
||||
{ pattern: "src/plugins/rosterview/tests/roster.js", type: 'module' },
|
||||
|
2353
package-lock.json
generated
2353
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -87,7 +87,7 @@
|
||||
"karma-jasmine": "^5.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.7.0",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"lerna": "^5.1.8",
|
||||
"lerna": "^5.5.1",
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"minimist": "^1.2.6",
|
||||
"po-loader": "0.6.1",
|
||||
|
@ -1,6 +1,5 @@
|
||||
import RosterContact from './contact.js';
|
||||
import log from "@converse/headless/log";
|
||||
import sum from 'lodash-es/sum';
|
||||
import { Collection } from "@converse/skeletor/src/collection";
|
||||
import { Model } from "@converse/skeletor/src/model";
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
|
@ -1,14 +0,0 @@
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import tpl_image_modal from "./templates/image.js";
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
id: 'image-modal',
|
||||
|
||||
toHTML () {
|
||||
return tpl_image_modal({
|
||||
'src': this.src,
|
||||
'onload': ev => (ev.target.parentElement.style.height = `${ev.target.height}px`)
|
||||
});
|
||||
}
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import tpl_message_versions_modal from "./templates/message-versions.js";
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
id: "message-versions-modal",
|
||||
toHTML () {
|
||||
return tpl_message_versions_modal(this.model);
|
||||
}
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
import { html } from "lit";
|
||||
import { __ } from 'i18n';
|
||||
import { modal_close_button, modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
|
||||
|
||||
export default (o) => {
|
||||
return html`
|
||||
<div class="modal-dialog fit-content" role="document">
|
||||
<div class="modal-content fit-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="message-versions-modal-label">${__('Image: ')}<a target="_blank" rel="noopener" href="${o.src}">${o.src}</a></h4>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body modal-body--image fit-content">
|
||||
<img class="chat-image" src="${o.src}" @load=${o.onload}>
|
||||
</div>
|
||||
<div class="modal-footer">${modal_close_button}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import 'shared/components/message-versions.js';
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { modal_close_button, modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
|
||||
|
||||
export default (model) => html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="message-versions-modal-label">${__('Message versions')}</h4>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<converse-message-versions .model=${model}></converse-message-versions>
|
||||
</div>
|
||||
<div class="modal-footer">${modal_close_button}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -1,71 +0,0 @@
|
||||
import avatar from 'shared/avatar/templates/avatar.js';
|
||||
import { __ } from 'i18n';
|
||||
import { html } from 'lit';
|
||||
import { modal_close_button, modal_header_close_button } from 'plugins/modal/templates/buttons.js'
|
||||
|
||||
|
||||
const remove_button = (o) => {
|
||||
const i18n_remove_contact = __('Remove as contact');
|
||||
return html`
|
||||
<button type="button" @click="${o.removeContact}" class="btn btn-danger remove-contact">
|
||||
<converse-icon
|
||||
class="fas fa-trash-alt"
|
||||
color="var(--text-color-lighten-15-percent)"
|
||||
size="1em"
|
||||
></converse-icon>
|
||||
${i18n_remove_contact}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
export default (o) => {
|
||||
const i18n_address = __('XMPP Address');
|
||||
const i18n_email = __('Email');
|
||||
const i18n_full_name = __('Full Name');
|
||||
const i18n_nickname = __('Nickname');
|
||||
const i18n_profile = __('The User\'s Profile Image');
|
||||
const i18n_refresh = __('Refresh');
|
||||
const i18n_role = __('Role');
|
||||
const i18n_url = __('URL');
|
||||
const avatar_data = {
|
||||
'alt_text': i18n_profile,
|
||||
'extra_classes': 'mb-3',
|
||||
'height': '120',
|
||||
'width': '120'
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="user-details-modal-label">${o.display_name}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${ o.image ? html`<div class="mb-4">${avatar(Object.assign(o, avatar_data))}</div>` : '' }
|
||||
${ o.fullname ? html`<p><label>${i18n_full_name}:</label> ${o.fullname}</p>` : '' }
|
||||
<p><label>${i18n_address}:</label> <a href="xmpp:${o.jid}">${o.jid}</a></p>
|
||||
${ o.nickname ? html`<p><label>${i18n_nickname}:</label> ${o.nickname}</p>` : '' }
|
||||
${ o.url ? html`<p><label>${i18n_url}:</label> <a target="_blank" rel="noopener" href="${o.url}">${o.url}</a></p>` : '' }
|
||||
${ o.email ? html`<p><label>${i18n_email}:</label> <a href="mailto:${o.email}">${o.email}</a></p>` : '' }
|
||||
${ o.role ? html`<p><label>${i18n_role}:</label> ${o.role}</p>` : '' }
|
||||
|
||||
<converse-omemo-fingerprints jid=${o.jid}></converse-omemo-fingerprints>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
${modal_close_button}
|
||||
<button type="button" class="btn btn-info refresh-contact">
|
||||
<converse-icon
|
||||
class="fa fa-refresh"
|
||||
color="var(--text-color-lighten-15-percent)"
|
||||
size="1em"
|
||||
></converse-icon>
|
||||
${i18n_refresh}</button>
|
||||
${ (o.allow_contact_removal && o.is_roster_contact) ? remove_button(o) : '' }
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import MUCBookmarkFormModal from './modal.js';
|
||||
import './modal.js';
|
||||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
|
||||
const { u } = converse.env;
|
||||
@ -34,6 +34,6 @@ export const bookmarkableChatRoomView = {
|
||||
showBookmarkModal(ev) {
|
||||
ev?.preventDefault();
|
||||
const jid = this.model.get('jid');
|
||||
api.modal.show(MUCBookmarkFormModal, { jid }, ev);
|
||||
api.modal.show('converse-bookmark-form-modal', { jid }, ev);
|
||||
}
|
||||
};
|
||||
|
@ -1,19 +1,20 @@
|
||||
import './form.js';
|
||||
import BaseModal from "plugins/modal/base.js";
|
||||
import tpl_modal from './templates/modal.js';
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import { html } from "lit";
|
||||
import { __ } from 'i18n';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
const MUCBookmarkFormModal = BaseModal.extend({
|
||||
id: "converse-bookmark-modal",
|
||||
export default class BookmarkFormModal extends BaseModal {
|
||||
|
||||
initialize (attrs) {
|
||||
this.jid = attrs.jid;
|
||||
this.affiliation = attrs.affiliation;
|
||||
BaseModal.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_modal(this);
|
||||
renderModal () {
|
||||
return html`
|
||||
<converse-muc-bookmark-form class="muc-form-container" jid="${this.jid}">
|
||||
</converse-muc-bookmark-form>`;
|
||||
}
|
||||
});
|
||||
|
||||
export default MUCBookmarkFormModal;
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Bookmark');
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-bookmark-form-modal', BookmarkFormModal);
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
|
||||
export default (o) => {
|
||||
const i18n_moderator_tools = __('Bookmark');
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="converse-modtools-modal-label">${i18n_moderator_tools}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<converse-muc-bookmark-form class="muc-form-container" jid="${o.jid}"></converse-muc-bookmark-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
@ -31,8 +31,8 @@ describe("A chat room", function () {
|
||||
expect(toggle.title).toBe('Bookmark this groupchat');
|
||||
toggle.click();
|
||||
|
||||
const modal = _converse.api.modal.get('converse-bookmark-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const modal = _converse.api.modal.get('converse-bookmark-form-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
/* Client uploads data:
|
||||
* --------------------
|
||||
@ -66,13 +66,13 @@ describe("A chat room", function () {
|
||||
* </iq>
|
||||
*/
|
||||
expect(view.model.get('bookmarked')).toBeFalsy();
|
||||
const form = await u.waitUntil(() => modal.el.querySelector('.chatroom-form'));
|
||||
const form = await u.waitUntil(() => modal.querySelector('.chatroom-form'));
|
||||
form.querySelector('input[name="name"]').value = 'Play's the Thing';
|
||||
form.querySelector('input[name="autojoin"]').checked = 'checked';
|
||||
form.querySelector('input[name="nick"]').value = 'JC';
|
||||
|
||||
const IQ_stanzas = _converse.connection.IQ_stanzas;
|
||||
modal.el.querySelector('converse-muc-bookmark-form .btn-primary').click();
|
||||
modal.querySelector('converse-muc-bookmark-form .btn-primary').click();
|
||||
|
||||
const sent_stanza = await u.waitUntil(
|
||||
() => IQ_stanzas.filter(s => sizzle('iq publish[node="storage:bookmarks"]', s).length).pop());
|
||||
@ -250,16 +250,16 @@ describe("A chat room", function () {
|
||||
bookmark_icon.click();
|
||||
expect(view.showBookmarkModal).toHaveBeenCalled();
|
||||
|
||||
const modal = _converse.api.modal.get('converse-bookmark-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const form = await u.waitUntil(() => modal.el.querySelector('.chatroom-form'));
|
||||
const modal = _converse.api.modal.get('converse-bookmark-form-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
const form = await u.waitUntil(() => modal.querySelector('.chatroom-form'));
|
||||
|
||||
expect(form.querySelector('input[name="name"]').value).toBe('The Play');
|
||||
expect(form.querySelector('input[name="autojoin"]').checked).toBeFalsy();
|
||||
expect(form.querySelector('input[name="nick"]').value).toBe('Othello');
|
||||
|
||||
// Remove the bookmark
|
||||
modal.el.querySelector('.button-remove').click();
|
||||
modal.querySelector('.button-remove').click();
|
||||
|
||||
await u.waitUntil(() => view.querySelector('.chatbox-title__text .fa-bookmark') === null);
|
||||
expect(_converse.bookmarks.length).toBe(0);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import MUCBookmarkFormModal from './modal.js';
|
||||
import './modal.js';
|
||||
import invokeMap from 'lodash-es/invokeMap';
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { __ } from 'i18n';
|
||||
@ -38,7 +38,7 @@ export async function removeBookmarkViaEvent (ev) {
|
||||
export function addBookmarkViaEvent (ev) {
|
||||
ev.preventDefault();
|
||||
const jid = ev.currentTarget.getAttribute('data-room-jid');
|
||||
api.modal.show(MUCBookmarkFormModal, { jid }, ev);
|
||||
api.modal.show('converse-bookmark-form-modal', { jid }, ev);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import UserDetailsModal from 'modals/user-details.js';
|
||||
import 'shared/modals/user-details.js';
|
||||
import tpl_chatbox_head from './templates/chat-head.js';
|
||||
import { CustomElement } from 'shared/components/element.js';
|
||||
import { __ } from 'i18n';
|
||||
@ -39,7 +39,7 @@ export default class ChatHeading extends CustomElement {
|
||||
|
||||
showUserDetailsModal (ev) {
|
||||
ev.preventDefault();
|
||||
api.modal.show(UserDetailsModal, { model: this.model }, ev);
|
||||
api.modal.show('converse-user-details-modal', { model: this.model }, ev);
|
||||
}
|
||||
|
||||
close (ev) {
|
||||
|
@ -343,9 +343,9 @@ describe("A Chat Message", function () {
|
||||
expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
|
||||
view.querySelector('.chat-msg__content .fa-edit').click();
|
||||
|
||||
const modal = _converse.api.modal.get('message-versions-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const older_msgs = modal.el.querySelectorAll('.older-msg');
|
||||
const modal = _converse.api.modal.get('converse-message-versions-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
const older_msgs = modal.querySelectorAll('.older-msg');
|
||||
expect(older_msgs.length).toBe(2);
|
||||
expect(older_msgs[0].textContent.includes('But soft, what light through yonder airlock breaks?')).toBe(true);
|
||||
expect(view.model.messages.models.length).toBe(1);
|
||||
|
@ -3,8 +3,6 @@
|
||||
const $msg = converse.env.$msg;
|
||||
const u = converse.env.utils;
|
||||
const Strophe = converse.env.Strophe;
|
||||
const sizzle = converse.env.sizzle;
|
||||
|
||||
|
||||
describe("The Controlbox", function () {
|
||||
|
||||
@ -124,10 +122,10 @@ describe("The Controlbox", function () {
|
||||
await mock.openControlBox(_converse);
|
||||
var cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.querySelector('.change-status').click()
|
||||
const modal = _converse.api.modal.get('modal-status-change');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
|
||||
modal.el.querySelector('[type="submit"]').click();
|
||||
const modal = _converse.api.modal.get('converse-chat-status-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
modal.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
|
||||
modal.querySelector('[type="submit"]').click();
|
||||
const sent_stanzas = _converse.connection.sent_stanzas;
|
||||
const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
|
||||
expect(Strophe.serialize(sent_presence)).toBe(
|
||||
@ -149,12 +147,12 @@ describe("The Controlbox", function () {
|
||||
await mock.openControlBox(_converse);
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.querySelector('.change-status').click()
|
||||
const modal = _converse.api.modal.get('modal-status-change');
|
||||
const modal = _converse.api.modal.get('converse-chat-status-modal');
|
||||
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
const msg = 'I am happy';
|
||||
modal.el.querySelector('input[name="status_message"]').value = msg;
|
||||
modal.el.querySelector('[type="submit"]').click();
|
||||
modal.querySelector('input[name="status_message"]').value = msg;
|
||||
modal.querySelector('[type="submit"]').click();
|
||||
const sent_stanzas = _converse.connection.sent_stanzas;
|
||||
const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
|
||||
expect(Strophe.serialize(sent_presence)).toBe(
|
||||
@ -171,193 +169,3 @@ describe("The Controlbox", function () {
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("The 'Add Contact' widget", function () {
|
||||
|
||||
it("opens up an add modal when you click on it",
|
||||
mock.initConverse([], {}, async function (_converse) {
|
||||
|
||||
await mock.waitForRoster(_converse, 'all');
|
||||
await mock.openControlBox(_converse);
|
||||
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.querySelector('.add-contact').click()
|
||||
const modal = _converse.api.modal.get('add-contact-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null);
|
||||
|
||||
const input_jid = modal.el.querySelector('input[name="jid"]');
|
||||
const input_name = modal.el.querySelector('input[name="name"]');
|
||||
input_jid.value = 'someone@';
|
||||
|
||||
const evt = new Event('input');
|
||||
input_jid.dispatchEvent(evt);
|
||||
expect(modal.el.querySelector('.suggestion-box li').textContent).toBe('someone@montague.lit');
|
||||
input_jid.value = 'someone@montague.lit';
|
||||
input_name.value = 'Someone';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
|
||||
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
|
||||
expect(Strophe.serialize(sent_stanza)).toEqual(
|
||||
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit" name="Someone"/></query>`+
|
||||
`</iq>`);
|
||||
}));
|
||||
|
||||
it("can be configured to not provide search suggestions",
|
||||
mock.initConverse([], {'autocomplete_add_contact': false}, async function (_converse) {
|
||||
|
||||
await mock.waitForRoster(_converse, 'all', 0);
|
||||
await mock.openControlBox(_converse);
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.querySelector('.add-contact').click()
|
||||
const modal = _converse.api.modal.get('add-contact-modal');
|
||||
expect(modal.jid_auto_complete).toBe(undefined);
|
||||
expect(modal.name_auto_complete).toBe(undefined);
|
||||
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null);
|
||||
const input_jid = modal.el.querySelector('input[name="jid"]');
|
||||
input_jid.value = 'someone@montague.lit';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
|
||||
const IQ_stanzas = _converse.connection.IQ_stanzas;
|
||||
const sent_stanza = await u.waitUntil(
|
||||
() => IQ_stanzas.filter(s => sizzle(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`, s).length).pop()
|
||||
);
|
||||
expect(Strophe.serialize(sent_stanza)).toEqual(
|
||||
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit"/></query>`+
|
||||
`</iq>`
|
||||
);
|
||||
}));
|
||||
|
||||
it("integrates with xhr_user_search_url to search for contacts",
|
||||
mock.initConverse([], { 'xhr_user_search_url': 'http://example.org/?' },
|
||||
async function (_converse) {
|
||||
|
||||
await mock.waitForRoster(_converse, 'all', 0);
|
||||
|
||||
class MockXHR extends XMLHttpRequest {
|
||||
open () {} // eslint-disable-line
|
||||
responseText = ''
|
||||
send () {
|
||||
this.responseText = JSON.stringify([
|
||||
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
|
||||
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
|
||||
]);
|
||||
this.onload();
|
||||
}
|
||||
}
|
||||
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');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
|
||||
// We only have autocomplete for the name input
|
||||
expect(modal.jid_auto_complete).toBe(undefined);
|
||||
expect(modal.name_auto_complete instanceof _converse.AutoComplete).toBe(true);
|
||||
|
||||
const input_el = modal.el.querySelector('input[name="name"]');
|
||||
input_el.value = 'marty';
|
||||
input_el.dispatchEvent(new Event('input'));
|
||||
await u.waitUntil(() => modal.el.querySelector('.suggestion-box li'), 1000);
|
||||
expect(modal.el.querySelectorAll('.suggestion-box li').length).toBe(1);
|
||||
const suggestion = modal.el.querySelector('.suggestion-box li');
|
||||
expect(suggestion.textContent).toBe('Marty McFly');
|
||||
|
||||
// Mock selection
|
||||
modal.name_auto_complete.select(suggestion);
|
||||
|
||||
expect(input_el.value).toBe('Marty McFly');
|
||||
expect(modal.el.querySelector('input[name="jid"]').value).toBe('marty@mcfly.net');
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
|
||||
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
|
||||
expect(Strophe.serialize(sent_stanza)).toEqual(
|
||||
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
|
||||
`</iq>`);
|
||||
window.XMLHttpRequest = XMLHttpRequestBackup;
|
||||
}));
|
||||
|
||||
it("can be configured to not provide search suggestions for XHR search results",
|
||||
mock.initConverse([],
|
||||
{ 'autocomplete_add_contact': false,
|
||||
'xhr_user_search_url': 'http://example.org/?' },
|
||||
async function (_converse) {
|
||||
|
||||
await mock.waitForRoster(_converse, 'all');
|
||||
await mock.openControlBox(_converse);
|
||||
|
||||
class MockXHR extends XMLHttpRequest {
|
||||
open () {} // eslint-disable-line
|
||||
responseText = ''
|
||||
send () {
|
||||
const value = modal.el.querySelector('input[name="name"]').value;
|
||||
if (value === 'existing') {
|
||||
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
|
||||
this.responseText = JSON.stringify([{"jid": contact_jid, "fullname": mock.cur_names[0]}]);
|
||||
} else if (value === 'romeo') {
|
||||
this.responseText = JSON.stringify([{"jid": "romeo@montague.lit", "fullname": "Romeo Montague"}]);
|
||||
} else if (value === 'ambiguous') {
|
||||
this.responseText = JSON.stringify([
|
||||
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
|
||||
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
|
||||
]);
|
||||
} else if (value === 'insufficient') {
|
||||
this.responseText = JSON.stringify([]);
|
||||
} else {
|
||||
this.responseText = JSON.stringify([{"jid": "marty@mcfly.net", "fullname": "Marty McFly"}]);
|
||||
}
|
||||
this.onload();
|
||||
}
|
||||
}
|
||||
|
||||
const XMLHttpRequestBackup = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXHR;
|
||||
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.querySelector('.add-contact').click()
|
||||
const modal = _converse.api.modal.get('add-contact-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
|
||||
expect(modal.jid_auto_complete).toBe(undefined);
|
||||
expect(modal.name_auto_complete).toBe(undefined);
|
||||
|
||||
const input_el = modal.el.querySelector('input[name="name"]');
|
||||
input_el.value = 'ambiguous';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
let feedback_el = modal.el.querySelector('.invalid-feedback');
|
||||
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
|
||||
feedback_el.textContent = '';
|
||||
|
||||
input_el.value = 'insufficient';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
feedback_el = modal.el.querySelector('.invalid-feedback');
|
||||
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
|
||||
feedback_el.textContent = '';
|
||||
|
||||
input_el.value = 'existing';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
feedback_el = modal.el.querySelector('.invalid-feedback');
|
||||
expect(feedback_el.textContent).toBe('This contact has already been added');
|
||||
|
||||
input_el.value = 'Marty McFly';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
|
||||
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
|
||||
expect(Strophe.serialize(sent_stanza)).toEqual(
|
||||
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
|
||||
`</iq>`);
|
||||
window.XMLHttpRequest = XMLHttpRequestBackup;
|
||||
}));
|
||||
});
|
||||
|
@ -1,19 +1,23 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import tpl_alert_modal from "./templates/alert.js";
|
||||
import { __ } from 'i18n';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
|
||||
const Alert = BootstrapModal.extend({
|
||||
id: 'alert-modal',
|
||||
export default class Alert extends BaseModal {
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render)
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_alert_modal(Object.assign({__}, this.model.toJSON()));
|
||||
super.initialize();
|
||||
this.listenTo(this.model, 'change', () => this.render())
|
||||
this.addEventListener('hide.bs.modal', () => this.remove(), false);
|
||||
}
|
||||
});
|
||||
|
||||
export default Alert;
|
||||
renderModal () {
|
||||
return tpl_alert_modal(this.model.toJSON());
|
||||
}
|
||||
|
||||
getModalTitle () {
|
||||
return this.model.get('title');
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-alert-modal', Alert);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Alert from './alert.js';
|
||||
import './alert.js';
|
||||
import Confirm from './confirm.js';
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
|
||||
let modals = [];
|
||||
|
||||
let modals_map = {};
|
||||
|
||||
const modal_api = {
|
||||
/**
|
||||
@ -17,13 +17,20 @@ const modal_api = {
|
||||
* 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 { Object } [properties] - Optional properties that will be set on a newly created modal instance.
|
||||
* @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);
|
||||
show (name, properties, ev) {
|
||||
let modal;
|
||||
if (typeof name === 'string') {
|
||||
modal = this.get(name) ?? this.create(name, properties);
|
||||
Object.assign(modal, properties);
|
||||
} else {
|
||||
// Legacy...
|
||||
const ModalClass = name;
|
||||
const id = ModalClass.id ?? properties.id;
|
||||
modal = this.get(id) ?? this.create(ModalClass, properties);
|
||||
}
|
||||
modal.show(ev);
|
||||
return modal;
|
||||
},
|
||||
@ -33,28 +40,44 @@ const modal_api = {
|
||||
* @param { String } id
|
||||
*/
|
||||
get (id) {
|
||||
return modals.filter(m => m.id == id).pop();
|
||||
return modals_map[id] ?? modals.filter(m => m.id == id).pop();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a modal of the passed-in type.
|
||||
* @param { Class } ModalClass
|
||||
* @param { String } name
|
||||
* @param { Object } [properties] - Optional properties that will be
|
||||
* set on the modal instance.
|
||||
*/
|
||||
create (ModalClass, properties) {
|
||||
const modal = new ModalClass(properties);
|
||||
create (name, properties) {
|
||||
let modal;
|
||||
if (typeof name === 'string') {
|
||||
const ModalClass = customElements.get(name);
|
||||
modal = modals_map[name] = new ModalClass(properties);
|
||||
} else {
|
||||
// Legacy...
|
||||
const ModalClass = name;
|
||||
modal = new ModalClass(properties);
|
||||
modals.push(modal);
|
||||
}
|
||||
return modal;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a particular modal
|
||||
* @param { View } modal
|
||||
* @param { String } name
|
||||
*/
|
||||
remove (modal) {
|
||||
remove (name) {
|
||||
let modal;
|
||||
if (typeof name === 'string') {
|
||||
modal = modals_map[name];
|
||||
delete modals_map[name];
|
||||
} else {
|
||||
// Legacy...
|
||||
modal = name;
|
||||
modals = modals.filter(m => m !== modal);
|
||||
modal.remove();
|
||||
}
|
||||
modal?.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -63,6 +86,7 @@ const modal_api = {
|
||||
removeAll () {
|
||||
modals.forEach(m => m.remove());
|
||||
modals = [];
|
||||
modals_map = {};
|
||||
}
|
||||
},
|
||||
|
||||
@ -157,7 +181,7 @@ const modal_api = {
|
||||
'level': level,
|
||||
'type': 'alert'
|
||||
})
|
||||
modal_api.modal.show(Alert, {model});
|
||||
modal_api.modal.show('converse-alert-modal', { model });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import bootstrap from "bootstrap.native";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_alert_component from "templates/alert.js";
|
||||
import tpl_alert_component from "./templates/modal-alert.js";
|
||||
import { View } from '@converse/skeletor/src/view.js';
|
||||
import { api, converse } from "@converse/headless/core";
|
||||
import { render } from 'lit';
|
||||
|
@ -1,35 +1,32 @@
|
||||
import BootstrapModal from './base.js';
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import tpl_prompt from "./templates/prompt.js";
|
||||
import { getOpenPromise } from '@converse/openpromise';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
export default class Confirm extends BaseModal {
|
||||
|
||||
const Confirm = BootstrapModal.extend({
|
||||
id: 'confirm-modal',
|
||||
events: {
|
||||
'submit .confirm': 'onConfimation'
|
||||
},
|
||||
constructor (options) {
|
||||
super(options);
|
||||
this.confirmation = getOpenPromise();
|
||||
}
|
||||
|
||||
initialize () {
|
||||
this.confirmation = getOpenPromise();
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render)
|
||||
this.el.addEventListener('closed.bs.modal', () => this.confirmation.reject(), false);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_prompt(this.model.toJSON());
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
if (!this.close_handler_registered) {
|
||||
this.el.addEventListener('closed.bs.modal', () => {
|
||||
super.initialize();
|
||||
this.listenTo(this.model, 'change', () => this.render())
|
||||
this.addEventListener('hide.bs.modal', () => {
|
||||
if (!this.confirmation.isResolved) {
|
||||
this.confirmation.reject()
|
||||
}
|
||||
}, false);
|
||||
this.close_handler_registered = true;
|
||||
}
|
||||
},
|
||||
|
||||
renderModal () {
|
||||
return tpl_prompt(this);
|
||||
}
|
||||
|
||||
getModalTitle () {
|
||||
this.model.get('title');
|
||||
}
|
||||
|
||||
onConfimation (ev) {
|
||||
ev.preventDefault();
|
||||
@ -53,6 +50,6 @@ const Confirm = BootstrapModal.extend({
|
||||
this.confirmation.resolve(fields);
|
||||
this.modal.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Confirm;
|
||||
api.elements.define('converse-confirm-modal', Confirm);
|
||||
|
70
src/plugins/modal/modal.js
Normal file
70
src/plugins/modal/modal.js
Normal file
@ -0,0 +1,70 @@
|
||||
import bootstrap from "bootstrap.native";
|
||||
import tpl_modal from './templates/modal.js';
|
||||
import { ElementView } from '@converse/skeletor/src/element.js';
|
||||
import { getOpenPromise } from '@converse/openpromise';
|
||||
|
||||
|
||||
import './styles/_modal.scss';
|
||||
|
||||
class BaseModal extends ElementView {
|
||||
|
||||
constructor (options) {
|
||||
super();
|
||||
this.className = 'modal';
|
||||
this.initialized = getOpenPromise();
|
||||
|
||||
// Allow properties to be set via passed in options
|
||||
Object.assign(this, options);
|
||||
setTimeout(() => this.insertIntoDOM());
|
||||
}
|
||||
|
||||
initialize () {
|
||||
this.modal = new bootstrap.Modal(this, {
|
||||
backdrop: true,
|
||||
keyboard: true
|
||||
});
|
||||
this.addEventListener('hide.bs.modal', () => this.onHide(), false);
|
||||
this.initialized.resolve();
|
||||
this.render()
|
||||
}
|
||||
|
||||
toHTML () {
|
||||
return tpl_modal(this);
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
// Intended to be overwritten
|
||||
return '';
|
||||
}
|
||||
|
||||
switchTab (ev) {
|
||||
ev?.stopPropagation();
|
||||
ev?.preventDefault();
|
||||
this.tab = ev.target.getAttribute('data-name');
|
||||
this.render();
|
||||
}
|
||||
|
||||
onHide () {
|
||||
this.modal.hide();
|
||||
}
|
||||
|
||||
insertIntoDOM () {
|
||||
const container_el = document.querySelector("#converse-modals");
|
||||
container_el.insertAdjacentElement('beforeEnd', this);
|
||||
}
|
||||
|
||||
alert (message, type='primary') {
|
||||
this.model.set('alert', { message, type });
|
||||
setTimeout(() => {
|
||||
this.model.set('alert', undefined);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
async show () {
|
||||
await this.initialized;
|
||||
this.modal.show();
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseModal;
|
@ -55,7 +55,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.fit-content {
|
||||
box-sizing: content-box;
|
||||
|
||||
@ -64,12 +63,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal-body--image {
|
||||
.chat-image {
|
||||
max-height: 99%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
.modal-footer {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
@ -103,43 +96,5 @@
|
||||
.btn {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#user-profile-modal {
|
||||
.profile-form {
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.fingerprint-removal {
|
||||
label {
|
||||
display: flex;
|
||||
padding: 0.75rem 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
font-size: 95%;
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fingerprints {
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.fingerprint-trust {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 95%;
|
||||
.fingerprint {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,8 @@
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
export default (o) => html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header ${o.level}">
|
||||
<h5 class="modal-title">${o.title}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
${ o.messages.map(message => html`<p>${message}</p>`) }
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
29
src/plugins/modal/templates/modal.js
Normal file
29
src/plugins/modal/templates/modal.js
Normal file
@ -0,0 +1,29 @@
|
||||
import tpl_alert_component from "./modal-alert.js";
|
||||
import { html } from "lit";
|
||||
import { modal_close_button, modal_header_close_button } from "plugins/modal/templates/buttons.js";
|
||||
|
||||
|
||||
export default (el) => {
|
||||
const alert = el.model?.get('alert');
|
||||
const level = el.model?.get('level') ?? '';
|
||||
return html`
|
||||
<div class="modal-dialog" role="document" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header ${level}">
|
||||
<h5 class="modal-title">${el.getModalTitle()}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert">
|
||||
${ alert ? tpl_alert_component({'type': `alert-${alert.type}`, 'message': alert.message}) : ''}
|
||||
</span>
|
||||
${ el.renderModal?.() ?? '' }
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
${ modal_close_button }
|
||||
${ el.renderModalFooter?.() ?? '' }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
@ -15,29 +15,16 @@ const tpl_field = (f) => html`
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
export default (o) => html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header ${o.level || ''}">
|
||||
<h5 class="modal-title">${o.title}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
<form class="converse-form converse-form--modal confirm" action="#">
|
||||
export default (el) => {
|
||||
return html`
|
||||
<form class="converse-form converse-form--modal confirm" action="#" @submit=${ev => el.onConfimation(ev)}>
|
||||
<div class="form-group">
|
||||
${ o.messages.map(message => html`<p>${message}</p>`) }
|
||||
${ el.model.get('messages')?.map(message => html`<p>${message}</p>`) }
|
||||
</div>
|
||||
${ o.fields.map(f => tpl_field(f)) }
|
||||
${ el.model.get('fields')?.map(f => tpl_field(f)) }
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">${__('OK')}</button>
|
||||
<input type="button" class="btn btn-secondary" data-dismiss="modal" value="${__('Cancel')}"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
</form>`;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import MUCInviteModal from './modals/muc-invite.js';
|
||||
import NicknameModal from './modals/nickname.js';
|
||||
import RoomDetailsModal from './modals/muc-details.js';
|
||||
import './modals/muc-details.js';
|
||||
import './modals/muc-invite.js';
|
||||
import './modals/nickname.js';
|
||||
import tpl_muc_head from './templates/muc-head.js';
|
||||
import { CustomElement } from 'shared/components/element.js';
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
@ -48,12 +48,12 @@ export default class MUCHeading extends CustomElement {
|
||||
|
||||
showRoomDetailsModal (ev) {
|
||||
ev.preventDefault();
|
||||
api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
|
||||
api.modal.show('converse-muc-details-modal', { 'model': this.model }, ev);
|
||||
}
|
||||
|
||||
showInviteModal (ev) {
|
||||
ev.preventDefault();
|
||||
api.modal.show(MUCInviteModal, { 'model': new Model(), 'chatroomview': this }, ev);
|
||||
api.modal.show('converse-muc-invite-modal', { 'model': new Model(), 'chatroomview': this }, ev);
|
||||
}
|
||||
|
||||
toggleTopic (ev) {
|
||||
@ -104,7 +104,7 @@ export default class MUCHeading extends CustomElement {
|
||||
buttons.push({
|
||||
'i18n_text': __('Nickname'),
|
||||
'i18n_title': __("Change the nickname you're using in this groupchat"),
|
||||
'handler': ev => api.modal.show(NicknameModal, { 'model': this.model }, ev),
|
||||
'handler': ev => api.modal.show('converse-muc-nickname-modal', { 'model': this.model }, ev),
|
||||
'a_class': 'open-nickname-modal',
|
||||
'icon_class': 'fa-smile',
|
||||
'name': 'nickname'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import tpl_add_muc from "../templates/add-muc.js";
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import tpl_add_muc from "./templates/add-muc.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
|
||||
@ -9,43 +9,27 @@ const u = converse.env.utils;
|
||||
const { Strophe } = converse.env;
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
persistent: true,
|
||||
id: 'add-chatroom-modal',
|
||||
|
||||
events: {
|
||||
'submit form.add-chatroom': 'openChatRoom',
|
||||
'keyup .roomjid-input': 'checkRoomidPolicy',
|
||||
'change .roomjid-input': 'checkRoomidPolicy'
|
||||
},
|
||||
export default class AddMUCModal extends BaseModal {
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change:muc_domain', this.render);
|
||||
super.initialize();
|
||||
this.listenTo(this.model, 'change:muc_domain', () => this.render());
|
||||
this.muc_roomid_policy_error_msg = null;
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
let placeholder = '';
|
||||
if (!api.settings.get('locked_muc_domain')) {
|
||||
const muc_domain = this.model.get('muc_domain') || api.settings.get('muc_domain');
|
||||
placeholder = muc_domain ? `name@${muc_domain}` : __('name@conference.example.org');
|
||||
}
|
||||
return tpl_add_muc(Object.assign(this.model.toJSON(), {
|
||||
'label_room_address': api.settings.get('muc_domain') ? __('Groupchat name') : __('Groupchat address'),
|
||||
'chatroom_placeholder': placeholder,
|
||||
'muc_roomid_policy_error_msg': this.muc_roomid_policy_error_msg,
|
||||
'muc_roomid_policy_hint': api.settings.get('muc_roomid_policy_hint')
|
||||
}));
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
this.el.addEventListener('shown.bs.modal', () => {
|
||||
this.el.querySelector('input[name="chatroom"]').focus();
|
||||
this.render();
|
||||
this.addEventListener('shown.bs.modal', () => {
|
||||
this.querySelector('input[name="chatroom"]').focus();
|
||||
}, false);
|
||||
},
|
||||
}
|
||||
|
||||
parseRoomDataFromEvent (form) {
|
||||
renderModal () {
|
||||
return tpl_add_muc(this);
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Enter a new Groupchat');
|
||||
}
|
||||
|
||||
parseRoomDataFromEvent (form) { // eslint-disable-line class-methods-use-this
|
||||
const data = new FormData(form);
|
||||
const jid = data.get('chatroom')?.trim();
|
||||
let nick;
|
||||
@ -61,10 +45,12 @@ export default BootstrapModal.extend({
|
||||
'jid': jid,
|
||||
'nick': nick
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
openChatRoom (ev) {
|
||||
ev.preventDefault();
|
||||
if (this.checkRoomidPolicy()) return;
|
||||
|
||||
const data = this.parseRoomDataFromEvent(ev.target);
|
||||
if (data.nick === "") {
|
||||
// Make sure defaults apply if no nick is provided.
|
||||
@ -77,14 +63,15 @@ export default BootstrapModal.extend({
|
||||
jid = data.jid
|
||||
this.model.setDomain(jid);
|
||||
}
|
||||
|
||||
api.rooms.open(jid, Object.assign(data, {jid}), true);
|
||||
this.modal.hide();
|
||||
ev.target.reset();
|
||||
},
|
||||
this.modal.hide();
|
||||
}
|
||||
|
||||
checkRoomidPolicy () {
|
||||
if (api.settings.get('muc_roomid_policy') && api.settings.get('muc_domain')) {
|
||||
let jid = this.el.querySelector('.roomjid-input').value;
|
||||
let jid = this.querySelector('converse-autocomplete input').value;
|
||||
if (api.settings.get('locked_muc_domain') || !u.isValidJID(jid)) {
|
||||
jid = `${Strophe.escapeNode(jid)}@${api.settings.get('muc_domain')}`;
|
||||
}
|
||||
@ -95,8 +82,11 @@ export default BootstrapModal.extend({
|
||||
this.muc_roomid_policy_error_msg = null;
|
||||
} else {
|
||||
this.muc_roomid_policy_error_msg = __('Groupchat id is invalid.');
|
||||
return true;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
api.elements.define('converse-add-muc-modal', AddMUCModal);
|
||||
|
@ -1,20 +1,24 @@
|
||||
import '../modtools.js';
|
||||
import BaseModal from "plugins/modal/base.js";
|
||||
import tpl_moderator_tools from './templates/moderator-tools.js';
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import { __ } from 'i18n';
|
||||
import { api } from "@converse/headless/core";
|
||||
import { html } from 'lit';
|
||||
|
||||
const ModeratorToolsModal = BaseModal.extend({
|
||||
id: "converse-modtools-modal",
|
||||
persistent: true,
|
||||
export default class ModeratorToolsModal extends BaseModal {
|
||||
|
||||
initialize (attrs) {
|
||||
this.jid = attrs.jid;
|
||||
this.affiliation = attrs.affiliation;
|
||||
BaseModal.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_moderator_tools(this);
|
||||
constructor (options) {
|
||||
super(options);
|
||||
this.id = "converse-modtools-modal";
|
||||
}
|
||||
});
|
||||
|
||||
export default ModeratorToolsModal;
|
||||
renderModal () {
|
||||
return html`<converse-modtools jid=${this.jid} affiliation=${this.affiliation}></converse-modtools>`;
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Moderator Tools');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
api.elements.define('converse-modtools-modal', ModeratorToolsModal);
|
||||
|
@ -1,21 +1,29 @@
|
||||
import BaseModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import tpl_muc_details from "./templates/muc-details.js";
|
||||
import { __ } from 'i18n';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
import '../styles/muc-details.scss';
|
||||
import '../styles/muc-details-modal.scss';
|
||||
|
||||
|
||||
export default BaseModal.extend({
|
||||
id: "muc-details-modal",
|
||||
export default class MUCDetailsModal extends BaseModal {
|
||||
|
||||
initialize () {
|
||||
BaseModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.listenTo(this.model.features, 'change', this.render);
|
||||
this.listenTo(this.model.occupants, 'add', this.render);
|
||||
this.listenTo(this.model.occupants, 'change', this.render);
|
||||
},
|
||||
super.initialize();
|
||||
this.listenTo(this.model, 'change', () => this.render());
|
||||
this.listenTo(this.model.features, 'change', () => this.render());
|
||||
this.listenTo(this.model.occupants, 'add', () => this.render());
|
||||
this.listenTo(this.model.occupants, 'change', () => this.render());
|
||||
}
|
||||
|
||||
toHTML () {
|
||||
renderModal () {
|
||||
return tpl_muc_details(this.model);
|
||||
}
|
||||
});
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Groupchat info for %1$s', this.model.getDisplayName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
api.elements.define('converse-muc-details-modal', MUCDetailsModal);
|
||||
|
@ -1,45 +1,35 @@
|
||||
import 'shared/autocomplete/index.js';
|
||||
import BaseModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import tpl_muc_invite_modal from "./templates/muc-invite.js";
|
||||
import { _converse, converse } from "@converse/headless/core";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
export default BaseModal.extend({
|
||||
id: "muc-invite-modal",
|
||||
export default class MUCInviteModal extends BaseModal {
|
||||
|
||||
initialize () {
|
||||
BaseModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.initInviteWidget();
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_muc_invite_modal(Object.assign(
|
||||
this.model.toJSON(), {
|
||||
'submitInviteForm': ev => this.submitInviteForm(ev)
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
initInviteWidget () {
|
||||
if (this.invite_auto_complete) {
|
||||
this.invite_auto_complete.destroy();
|
||||
super.initialize();
|
||||
this.listenTo(this.model, 'change', () => this.render());
|
||||
}
|
||||
|
||||
renderModal () {
|
||||
return tpl_muc_invite_modal(this);
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Invite someone to this groupchat');
|
||||
}
|
||||
|
||||
getAutoCompleteList () { // eslint-disable-line class-methods-use-this
|
||||
return _converse.roster.map(i => ({'label': i.getDisplayName(), 'value': i.get('jid')}));
|
||||
}
|
||||
const list = _converse.roster.map(i => ({'label': i.getDisplayName(), 'value': i.get('jid')}));
|
||||
const el = this.el.querySelector('.suggestion-box').parentElement;
|
||||
this.invite_auto_complete = new _converse.AutoComplete(el, {
|
||||
'min_chars': 1,
|
||||
'list': list
|
||||
});
|
||||
},
|
||||
|
||||
submitInviteForm (ev) {
|
||||
ev.preventDefault();
|
||||
// TODO: Add support for sending an invite to multiple JIDs
|
||||
const data = new FormData(ev.target);
|
||||
const jid = data.get('invitee_jids');
|
||||
const jid = data.get('invitee_jids')?.trim();
|
||||
const reason = data.get('reason');
|
||||
if (u.isValidJID(jid)) {
|
||||
// TODO: Create and use API here
|
||||
@ -49,4 +39,6 @@ export default BaseModal.extend({
|
||||
this.model.set({'invalid_invite_jid': true});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
api.elements.define('converse-muc-invite-modal', MUCInviteModal);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import head from "lodash-es/head";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_muc_list from "../templates/muc-list.js";
|
||||
import tpl_muc_description from "../templates/muc-description.js";
|
||||
import tpl_muc_list from "../templates/muc-list.js";
|
||||
import tpl_spinner from "templates/spinner.js";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
@ -65,28 +65,25 @@ function toggleRoomInfo (ev) {
|
||||
}
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
id: "muc-list-modal",
|
||||
persistent: true,
|
||||
export default class MUCListModal extends BaseModal {
|
||||
|
||||
initialize () {
|
||||
constructor (options) {
|
||||
super(options);
|
||||
this.items = [];
|
||||
this.loading_items = false;
|
||||
}
|
||||
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
initialize () {
|
||||
super.initialize();
|
||||
this.listenTo(this.model, 'change:muc_domain', this.onDomainChange);
|
||||
this.listenTo(this.model, 'change:feedback_text', () => this.render());
|
||||
|
||||
|
||||
this.el.addEventListener('shown.bs.modal', () => api.settings.get('locked_muc_domain')
|
||||
? this.updateRoomsList()
|
||||
: this.el.querySelector('input[name="server"]').focus()
|
||||
);
|
||||
this.addEventListener('shown.bs.modal', () => api.settings.get('locked_muc_domain') && this.updateRoomsList());
|
||||
|
||||
this.model.save('feedback_text', '');
|
||||
},
|
||||
}
|
||||
|
||||
toHTML () {
|
||||
renderModal () {
|
||||
return tpl_muc_list(
|
||||
Object.assign(this.model.toJSON(), {
|
||||
'show_form': !api.settings.get('locked_muc_domain'),
|
||||
@ -98,7 +95,11 @@ export default BootstrapModal.extend({
|
||||
'submitForm': ev => this.showRooms(ev),
|
||||
'toggleRoomInfo': ev => this.toggleRoomInfo(ev)
|
||||
}));
|
||||
},
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Query for Groupchats');
|
||||
}
|
||||
|
||||
openRoom (ev) {
|
||||
ev.preventDefault();
|
||||
@ -106,16 +107,16 @@ export default BootstrapModal.extend({
|
||||
const name = ev.target.getAttribute('data-room-name');
|
||||
this.modal.hide();
|
||||
api.rooms.open(jid, {'name': name}, true);
|
||||
},
|
||||
}
|
||||
|
||||
toggleRoomInfo (ev) {
|
||||
toggleRoomInfo (ev) { // eslint-disable-line
|
||||
ev.preventDefault();
|
||||
toggleRoomInfo(ev);
|
||||
},
|
||||
}
|
||||
|
||||
onDomainChange () {
|
||||
api.settings.get('auto_list_rooms') && this.updateRoomsList();
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the IQ stanza returned from the server, containing
|
||||
@ -136,7 +137,7 @@ export default BootstrapModal.extend({
|
||||
}
|
||||
this.render();
|
||||
return true;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an IQ stanza to the server asking for all groupchats
|
||||
@ -152,7 +153,7 @@ export default BootstrapModal.extend({
|
||||
api.sendIQ(iq)
|
||||
.then(iq => this.onRoomsFound(iq))
|
||||
.catch(() => this.onRoomsFound())
|
||||
},
|
||||
}
|
||||
|
||||
showRooms (ev) {
|
||||
ev.preventDefault();
|
||||
@ -162,13 +163,15 @@ export default BootstrapModal.extend({
|
||||
const data = new FormData(ev.target);
|
||||
this.model.setDomain(data.get('server'));
|
||||
this.updateRoomsList();
|
||||
},
|
||||
}
|
||||
|
||||
setDomainFromEvent (ev) {
|
||||
this.model.setDomain(ev.target.value);
|
||||
},
|
||||
}
|
||||
|
||||
setNick (ev) {
|
||||
this.model.save({nick: ev.target.value});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
api.elements.define('converse-muc-list-modal', MUCListModal);
|
||||
|
@ -1,15 +1,17 @@
|
||||
import tpl_nickname from "./templates/nickname.js";
|
||||
import BaseModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import { __ } from 'i18n';
|
||||
import { api } from "@converse/headless/core.js";
|
||||
import { html } from 'lit';
|
||||
|
||||
export default BaseModal.extend({
|
||||
id: 'change-nickname-modal',
|
||||
export default class MUCNicknameModal extends BaseModal {
|
||||
|
||||
initialize (attrs) {
|
||||
this.model = attrs.model;
|
||||
BaseModal.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
renderModal () {
|
||||
return html`<converse-muc-nickname-form jid="${this.model.get('jid')}"></converse-muc-nickname-form>`;
|
||||
}
|
||||
|
||||
toHTML () {
|
||||
return tpl_nickname(this);
|
||||
},
|
||||
});
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Change your nickname');
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-muc-nickname-modal', MUCNicknameModal);
|
||||
|
@ -1,16 +1,14 @@
|
||||
import BaseModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import tpl_occupant_modal from "./templates/occupant.js";
|
||||
import { _converse, api } from "@converse/headless/core";
|
||||
|
||||
|
||||
const OccupantModal = BaseModal.extend({
|
||||
id: "muc-occupant",
|
||||
export default class OccupantModal extends BaseModal {
|
||||
|
||||
initialize () {
|
||||
BaseModal.prototype.initialize.apply(this, arguments);
|
||||
if (this.model) {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
}
|
||||
super.initialize()
|
||||
const model = this.model ?? this.message;
|
||||
this.listenTo(model, 'change', () => this.render());
|
||||
/**
|
||||
* Triggered once the OccupantModal has been initialized
|
||||
* @event _converse#occupantModalInitialized
|
||||
@ -18,7 +16,7 @@ const OccupantModal = BaseModal.extend({
|
||||
* @example _converse.api.listen.on('occupantModalInitialized', data);
|
||||
*/
|
||||
api.trigger('occupantModalInitialized', { 'model': this.model, 'message': this.message });
|
||||
},
|
||||
}
|
||||
|
||||
getVcard () {
|
||||
const model = this.model ?? this.message;
|
||||
@ -27,22 +25,24 @@ const OccupantModal = BaseModal.extend({
|
||||
}
|
||||
const jid = model?.get('jid') || model?.get('from');
|
||||
return jid ? _converse.vcards.get(jid) : null;
|
||||
},
|
||||
}
|
||||
|
||||
toHTML () {
|
||||
renderModal () {
|
||||
const model = this.model ?? this.message;
|
||||
const jid = model?.get('jid');
|
||||
const vcard = this.getVcard();
|
||||
const display_name = model?.getDisplayName();
|
||||
const nick = model.get('nick');
|
||||
const occupant_id = model.get('occupant_id');
|
||||
const role = this.model?.get('role');
|
||||
const affiliation = this.model?.get('affiliation');
|
||||
const hats = this.model?.get('hats')?.length ? this.model.get('hats') : null;
|
||||
return tpl_occupant_modal({ jid, vcard, display_name, nick, occupant_id, role, affiliation, hats });
|
||||
return tpl_occupant_modal({ jid, vcard, nick, occupant_id, role, affiliation, hats });
|
||||
}
|
||||
});
|
||||
|
||||
_converse.OccupantModal = OccupantModal;
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
const model = this.model ?? this.message;
|
||||
return model?.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
export default OccupantModal;
|
||||
api.elements.define('converse-muc-occupant-modal', OccupantModal);
|
||||
|
57
src/plugins/muc-views/modals/templates/add-muc.js
Normal file
57
src/plugins/muc-views/modals/templates/add-muc.js
Normal file
@ -0,0 +1,57 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { __ } from 'i18n';
|
||||
import { api } from '@converse/headless/core.js';
|
||||
import { html } from "lit";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { getAutoCompleteList } from "../../search.js";
|
||||
|
||||
|
||||
const nickname_input = (el) => {
|
||||
const i18n_nickname = __('Nickname');
|
||||
const i18n_required_field = __('This field is required');
|
||||
return html`
|
||||
<div class="form-group" >
|
||||
<label for="nickname">${i18n_nickname}:</label>
|
||||
<input type="text"
|
||||
title="${i18n_required_field}"
|
||||
required="required"
|
||||
name="nickname"
|
||||
value="${el.model.get('nick') || ''}"
|
||||
class="form-control"/>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export default (el) => {
|
||||
const i18n_join = __('Join');
|
||||
const muc_domain = el.model.get('muc_domain') || api.settings.get('muc_domain');
|
||||
|
||||
let placeholder = '';
|
||||
if (!api.settings.get('locked_muc_domain')) {
|
||||
placeholder = muc_domain ? `name@${muc_domain}` : __('name@conference.example.org');
|
||||
}
|
||||
|
||||
const label_room_address = muc_domain ? __('Groupchat name') : __('Groupchat address');
|
||||
const muc_roomid_policy_error_msg = el.muc_roomid_policy_error_msg;
|
||||
const muc_roomid_policy_hint = api.settings.get('muc_roomid_policy_hint');
|
||||
return html`
|
||||
<form class="converse-form add-chatroom" @submit=${(ev) => el.openChatRoom(ev)}>
|
||||
<div class="form-group">
|
||||
<label for="chatroom">${label_room_address}:</label>
|
||||
${ (muc_roomid_policy_error_msg) ? html`<label class="roomid-policy-error">${muc_roomid_policy_error_msg}</label>` : '' }
|
||||
<converse-autocomplete
|
||||
.getAutoCompleteList=${getAutoCompleteList}
|
||||
?autofocus=${true}
|
||||
min_chars="3"
|
||||
position="below"
|
||||
placeholder="${placeholder}"
|
||||
class="add-muc-autocomplete"
|
||||
name="chatroom">
|
||||
</converse-autocomplete>
|
||||
</div>
|
||||
${ muc_roomid_policy_hint ? html`<div class="form-group">${unsafeHTML(DOMPurify.sanitize(muc_roomid_policy_hint, {'ALLOWED_TAGS': ['b', 'br', 'em']}))}</div>` : '' }
|
||||
${ !api.settings.get('locked_muc_nickname') ? nickname_input(el) : '' }
|
||||
<input type="submit" class="btn btn-primary" name="join" value="${i18n_join || ''}" ?disabled=${muc_roomid_policy_error_msg}/>
|
||||
</form>
|
||||
`;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
|
||||
export default (o) => {
|
||||
const i18n_moderator_tools = __('Moderator Tools');
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="converse-modtools-modal-label">${i18n_moderator_tools}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<converse-modtools jid=${o.jid} affiliation=${o.affiliation}></converse-modtools>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { modal_close_button, modal_header_close_button } from "plugins/modal/templates/buttons.js";
|
||||
|
||||
|
||||
const subject = (o) => {
|
||||
@ -16,7 +15,6 @@ const subject = (o) => {
|
||||
export default (model) => {
|
||||
const o = model.toJSON();
|
||||
const config = model.config.toJSON();
|
||||
const display_name = __('Groupchat info for %1$s', model.getDisplayName());
|
||||
const features = model.features.toJSON();
|
||||
const num_occupants = model.occupants.filter(o => o.get('show') !== 'offline').length;
|
||||
|
||||
@ -51,14 +49,6 @@ export default (model) => {
|
||||
const i18n_temporary = __('Temporary');
|
||||
const i18n_temporary_help = __('This groupchat will disappear once the last person leaves');
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="muc-details-modal-label">${display_name}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
<div class="room-info">
|
||||
<p class="room-info"><strong>${i18n_name}</strong>: ${o.name}</p>
|
||||
<p class="room-info"><strong>${i18n_address}</strong>: <converse-rich-text text="xmpp:${o.jid}?join"></converse-rich-text></p>
|
||||
@ -75,7 +65,7 @@ export default (model) => {
|
||||
${ features.membersonly ? html`<li class="feature" ><converse-icon size="1em" class="fa fa-address-book"></converse-icon>${i18n_members_only} - <em>${i18n_members_help}</em></li>` : '' }
|
||||
${ features.open ? html`<li class="feature" ><converse-icon size="1em" class="fa fa-globe"></converse-icon>${i18n_open} - <em>${i18n_open_help}</em></li>` : '' }
|
||||
${ features.persistent ? html`<li class="feature" ><converse-icon size="1em" class="fa fa-save"></converse-icon>${i18n_persistent} - <em>${i18n_persistent_help}</em></li>` : '' }
|
||||
${ features.temporary ? html`<li class="feature" ><converse-icon size="1em" class="fa fa-snowflake-o"></converse-icon>${i18n_temporary} - <em>${i18n_temporary_help}</em></li>` : '' }
|
||||
${ features.temporary ? html`<li class="feature" ><converse-icon size="1em" class="fa fa-snowflake"></converse-icon>${i18n_temporary} - <em>${i18n_temporary_help}</em></li>` : '' }
|
||||
${ features.nonanonymous ? html`<li class="feature" ><converse-icon size="1em" class="fa fa-id-card"></converse-icon>${i18n_not_anonymous} - <em>${i18n_not_anonymous_help}</em></li>` : '' }
|
||||
${ features.semianonymous ? html`<li class="feature" ><converse-icon size="1em" class="fa fa-user-secret"></converse-icon>${i18n_semi_anon} - <em>${i18n_semi_anon_help}</em></li>` : '' }
|
||||
${ features.moderated ? html`<li class="feature" ><converse-icon size="1em" class="fa fa-gavel"></converse-icon>${i18n_moderated} - <em>${i18n_moderated_help}</em></li>` : '' }
|
||||
@ -84,10 +74,5 @@ export default (model) => {
|
||||
</ul>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">${modal_close_button}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -1,49 +1,35 @@
|
||||
import { html } from "lit";
|
||||
import { __ } from 'i18n';
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
|
||||
|
||||
export default (o) => {
|
||||
export default (el) => {
|
||||
const i18n_invite = __('Invite');
|
||||
const i18n_invite_heading = __('Invite someone to this groupchat');
|
||||
const i18n_jid_placeholder = __('user@example.org');
|
||||
const i18n_error_message = __('Please enter a valid XMPP address');
|
||||
const i18n_invite_label = __('XMPP Address');
|
||||
const i18n_reason = __('Optional reason for the invitation');
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="add-chatroom-modal-label">${i18n_invite_heading}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
<div class="suggestion-box room-invite">
|
||||
<form @submit=${o.submitInviteForm}>
|
||||
<form class="converse-form" @submit=${(ev) => el.submitInviteForm(ev)}>
|
||||
<div class="form-group">
|
||||
<label class="clearfix" for="invitee_jids">${i18n_invite_label}:</label>
|
||||
${ o.invalid_invite_jid ? html`<div class="error error-feedback">${i18n_error_message}</div>` : '' }
|
||||
<input class="form-control suggestion-box__input"
|
||||
${ el.model.get('invalid_invite_jid') ? html`<div class="error error-feedback">${i18n_error_message}</div>` : '' }
|
||||
<converse-autocomplete
|
||||
.getAutoCompleteList=${() => el.getAutoCompleteList()}
|
||||
?autofocus=${true}
|
||||
min_chars="1"
|
||||
position="below"
|
||||
required="required"
|
||||
name="invitee_jids"
|
||||
id="invitee_jids"
|
||||
placeholder="${i18n_jid_placeholder}"
|
||||
type="text"/>
|
||||
<span class="suggestion-box__additions visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span>
|
||||
<ul class="suggestion-box__results suggestion-box__results--below" hidden=""></ul>
|
||||
placeholder="${i18n_jid_placeholder}">
|
||||
</converse-autocomplete>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${i18n_reason}:</label>
|
||||
<textarea class="form-control" name="reason"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">${i18n_invite}</button>
|
||||
<input type="submit" class="btn btn-primary" value="${i18n_invite}"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
|
||||
export default (modal) => {
|
||||
const jid = modal.model.get('jid');
|
||||
const i18n_heading = __('Change your nickname');
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="converse-modtools-modal-label">
|
||||
${i18n_heading}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<converse-muc-nickname-form jid="${jid}"></converse-muc-nickname-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
@ -1,18 +1,10 @@
|
||||
import 'shared/avatar/avatar.js';
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { modal_close_button, modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
|
||||
|
||||
export default (o) => {
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="user-details-modal-label">${o.display_name}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body" class="d-flex">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<converse-avatar
|
||||
@ -44,11 +36,5 @@ export default (o) => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
${modal_close_button}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ export default class ModeratorTools extends CustomElement {
|
||||
muc: { type: Object, attribute: false },
|
||||
role: { type: String },
|
||||
roles_filter: { type: String, attribute: false },
|
||||
tab: { type: String },
|
||||
users_with_affiliation: { type: Array, attribute: false },
|
||||
users_with_role: { type: Array, attribute: false },
|
||||
};
|
||||
@ -32,6 +33,7 @@ export default class ModeratorTools extends CustomElement {
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
this.tab = 'affiliations';
|
||||
this.affiliation = '';
|
||||
this.affiliations_filter = '';
|
||||
this.role = '';
|
||||
@ -72,6 +74,7 @@ export default class ModeratorTools extends CustomElement {
|
||||
'queryable_roles': ROLES.filter(a => !api.settings.get('modtools_disable_query').includes(a)),
|
||||
'roles_filter': this.roles_filter,
|
||||
'switchTab': ev => this.switchTab(ev),
|
||||
'tab': this.tab,
|
||||
'toggleForm': ev => this.toggleForm(ev),
|
||||
'users_with_affiliation': this.users_with_affiliation,
|
||||
'users_with_role': this.users_with_role,
|
||||
@ -81,6 +84,13 @@ export default class ModeratorTools extends CustomElement {
|
||||
}
|
||||
}
|
||||
|
||||
switchTab (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.tab = ev.target.getAttribute('data-name');
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
async onSearchAffiliationChange () {
|
||||
if (!this.affiliation) {
|
||||
return;
|
||||
|
@ -1,4 +1,5 @@
|
||||
#add-chatroom-modal {
|
||||
converse-add-muc-modal {
|
||||
.add-chatroom {
|
||||
converse-autocomplete {
|
||||
.suggestion-box__results--below {
|
||||
height: 10em;
|
||||
@ -9,4 +10,5 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
@import "./controlbox.scss";
|
||||
@import "./muc.scss";
|
||||
@import "./muc-details-modal.scss";
|
||||
|
||||
converse-muc-disconnected,
|
||||
converse-muc-destroyed {
|
||||
|
@ -1,8 +1,14 @@
|
||||
#muc-details-modal {
|
||||
converse-muc-details-modal {
|
||||
.features-list {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.room-info {
|
||||
strong {
|
||||
color: var(--muc-color);
|
||||
}
|
||||
}
|
||||
|
||||
.chatroom-features {
|
||||
width: 100%;
|
||||
.features-list {
|
||||
@ -13,9 +19,8 @@
|
||||
padding-right: 0;
|
||||
font-size: 1em;
|
||||
cursor: help;
|
||||
.fa {
|
||||
converse-icon {
|
||||
margin-right: 0.5em;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
#muc-details-modal {
|
||||
.room-info {
|
||||
strong {
|
||||
color: var(--muc-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { __ } from 'i18n';
|
||||
import { api } from '@converse/headless/core.js';
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { getAutoCompleteList } from "../search.js";
|
||||
|
||||
|
||||
const nickname_input = (o) => {
|
||||
const i18n_nickname = __('Nickname');
|
||||
const i18n_required_field = __('This field is required');
|
||||
return html`
|
||||
<div class="form-group" >
|
||||
<label for="nickname">${i18n_nickname}:</label>
|
||||
<input type="text" title="${i18n_required_field}" required="required" name="nickname" value="${o.nick || ''}" class="form-control"/>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
export default (o) => {
|
||||
const i18n_join = __('Join');
|
||||
const i18n_enter = __('Enter a new Groupchat');
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="add-chatroom-modal-label">${i18n_enter}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
<form class="converse-form add-chatroom">
|
||||
<div class="form-group">
|
||||
<label for="chatroom">${o.label_room_address}:</label>
|
||||
${ (o.muc_roomid_policy_error_msg) ? html`<label class="roomid-policy-error">${o.muc_roomid_policy_error_msg}</label>` : '' }
|
||||
<converse-autocomplete
|
||||
.getAutoCompleteList=${getAutoCompleteList}
|
||||
?autofocus=${true}
|
||||
min_chars="3"
|
||||
position="below"
|
||||
placeholder="${o.chatroom_placeholder}"
|
||||
class="add-muc-autocomplete"
|
||||
name="chatroom">
|
||||
</converse-autocomplete>
|
||||
</div>
|
||||
${ o.muc_roomid_policy_hint ? html`<div class="form-group">${unsafeHTML(DOMPurify.sanitize(o.muc_roomid_policy_hint, {'ALLOWED_TAGS': ['b', 'br', 'em']}))}</div>` : '' }
|
||||
${ !api.settings.get('locked_muc_nickname') ? nickname_input(o) : '' }
|
||||
<input type="submit" class="btn btn-primary" name="join" value="${i18n_join || ''}" ?disabled=${o.muc_roomid_policy_error_msg}>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
@ -146,13 +146,25 @@ const affiliation_list_item = (o) => html`
|
||||
`;
|
||||
|
||||
|
||||
const tpl_navigation = () => html`
|
||||
const tpl_navigation = (o) => html`
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link active" id="affiliations-tab" href="#affiliations-tabpanel" aria-controls="affiliations-tabpanel" role="tab" data-toggle="tab">Affiliations</a>
|
||||
<a class="nav-link ${o.tab === "affiliations" ? "active" : ""}"
|
||||
id="affiliations-tab"
|
||||
href="#affiliations-tabpanel"
|
||||
aria-controls="affiliations-tabpanel"
|
||||
role="tab"
|
||||
data-name="affiliations"
|
||||
@click=${o.switchTab}>Affiliations</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link" id="roles-tab" href="#roles-tabpanel" aria-controls="roles-tabpanel" role="tab" data-toggle="tab">Roles</a>
|
||||
<a class="nav-link ${o.tab === "roles" ? "active" : ""}"
|
||||
id="roles-tab"
|
||||
href="#roles-tabpanel"
|
||||
aria-controls="roles-tabpanel"
|
||||
role="tab"
|
||||
data-name="roles"
|
||||
@click=${o.switchTab}>Roles</a>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
@ -178,12 +190,12 @@ export default (o) => {
|
||||
const show_both_tabs = o.queryable_roles.length && o.queryable_affiliations.length;
|
||||
return html`
|
||||
${o.alert_message ? html`<div class="alert alert-${o.alert_type}" role="alert">${o.alert_message}</div>` : '' }
|
||||
${ show_both_tabs ? tpl_navigation() : '' }
|
||||
${ show_both_tabs ? tpl_navigation(o) : '' }
|
||||
|
||||
<div class="tab-content">
|
||||
|
||||
${ o.queryable_affiliations.length ? html`
|
||||
<div class="tab-pane tab-pane--columns ${ o.queryable_affiliations.length ? 'active' : ''}" id="affiliations-tabpanel" role="tabpanel" aria-labelledby="affiliations-tab">
|
||||
<div class="tab-pane tab-pane--columns ${ o.tab === 'affiliations' ? 'active' : ''}" id="affiliations-tabpanel" role="tabpanel" aria-labelledby="affiliations-tab">
|
||||
<form class="converse-form query-affiliation" @submit=${o.queryAffiliation}>
|
||||
<p class="helptext pb-3">${i18n_helptext_affiliation}</p>
|
||||
<div class="form-group">
|
||||
@ -225,7 +237,7 @@ export default (o) => {
|
||||
</div>` : '' }
|
||||
|
||||
${ o.queryable_roles.length ? html`
|
||||
<div class="tab-pane tab-pane--columns ${ !show_both_tabs && o.queryable_roles.length ? 'active' : ''}" id="roles-tabpanel" role="tabpanel" aria-labelledby="roles-tab">
|
||||
<div class="tab-pane tab-pane--columns ${ o.tab === 'roles' ? 'active' : ''}" id="roles-tabpanel" role="tabpanel" aria-labelledby="roles-tab">
|
||||
<form class="converse-form query-role" @submit=${o.queryRole}>
|
||||
<p class="helptext pb-3">${i18n_helptext_role}</p>
|
||||
<div class="form-group">
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { modal_close_button, modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
import spinner from "templates/spinner.js";
|
||||
|
||||
|
||||
@ -14,6 +13,7 @@ const form = (o) => {
|
||||
<div class="form-group">
|
||||
<label for="chatroom">${i18n_server_address}:</label>
|
||||
<input type="text"
|
||||
autofocus
|
||||
@change=${o.setDomainFromEvent}
|
||||
value="${o.muc_domain || ''}"
|
||||
required="required"
|
||||
@ -51,25 +51,12 @@ const tpl_item = (o, item) => {
|
||||
|
||||
|
||||
export default (o) => {
|
||||
const i18n_list_chatrooms = __('Query for Groupchats');
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="muc-list-modal-label">${i18n_list_chatrooms}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<span class="modal-alert"></span>
|
||||
${o.show_form ? form(o) : '' }
|
||||
<ul class="available-chatrooms list-group">
|
||||
${ o.loading_items ? html`<li class="list-group-item"> ${spinner()} </li>` : '' }
|
||||
${ o.feedback_text ? html`<li class="list-group-item active">${ o.feedback_text }</li>` : '' }
|
||||
${repeat(o.items, item => item.jid, item => tpl_item(o, item))}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">${modal_close_button}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ export default (el) => {
|
||||
const validation_message = el.model?.get('nickname_validation_message');
|
||||
|
||||
return html`
|
||||
<div class="chatroom-form-container muc-nickname-form"
|
||||
<div class="chatroom-form-container muc-nickname-form">
|
||||
<form class="converse-form chatroom-form converse-centered-form"
|
||||
@submit=${ev => el.submitNickname(ev)}>
|
||||
<form class="converse-form chatroom-form converse-centered-form">
|
||||
<fieldset class="form-group">
|
||||
<label>${i18n_heading}</label>
|
||||
<p class="validation-message">${validation_message}</p>
|
||||
@ -26,7 +26,10 @@ export default (el) => {
|
||||
placeholder="${i18n_nickname}"/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input type="submit" class="btn btn-primary" name="join" value="${i18n_join}"/>
|
||||
<input type="submit"
|
||||
class="btn btn-primary"
|
||||
name="join"
|
||||
value="${i18n_join}"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>`;
|
||||
|
@ -60,9 +60,9 @@ describe("A Groupchat Message", function () {
|
||||
expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
|
||||
const edit = await u.waitUntil(() => view.querySelector('.chat-msg__content .fa-edit'));
|
||||
edit.click();
|
||||
const modal = _converse.api.modal.get('message-versions-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const older_msgs = modal.el.querySelectorAll('.older-msg');
|
||||
const modal = _converse.api.modal.get('converse-message-versions-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
const older_msgs = modal.querySelectorAll('.older-msg');
|
||||
expect(older_msgs.length).toBe(2);
|
||||
expect(older_msgs[0].textContent.includes('But soft, what light through yonder airlock breaks?')).toBe(true);
|
||||
expect(older_msgs[1].textContent.includes('But soft, what light through yonder chimney breaks?')).toBe(true);
|
||||
@ -151,9 +151,9 @@ describe("A Groupchat Message", function () {
|
||||
expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
|
||||
const edit = await u.waitUntil(() => view.querySelector('.chat-msg__content .fa-edit'));
|
||||
edit.click();
|
||||
const modal = _converse.api.modal.get('message-versions-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const older_msgs = modal.el.querySelectorAll('.older-msg');
|
||||
const modal = _converse.api.modal.get('converse-message-versions-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
const older_msgs = modal.querySelectorAll('.older-msg');
|
||||
expect(older_msgs.length).toBe(2);
|
||||
expect(older_msgs[0].textContent.includes('But soft, what light through yonder airlock breaks?')).toBe(true);
|
||||
expect(older_msgs[1].textContent.includes('But soft, what light through yonder chimney breaks?')).toBe(true);
|
||||
|
@ -14,7 +14,7 @@ async function openModtools (_converse, view) {
|
||||
const message_form = view.querySelector('converse-muc-message-form');
|
||||
message_form.onKeyDown(enter);
|
||||
const modal = await u.waitUntil(() => _converse.api.modal.get('converse-modtools-modal'));
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
return modal;
|
||||
}
|
||||
|
||||
@ -37,18 +37,18 @@ describe("The groupchat moderator tool", function () {
|
||||
await u.waitUntil(() => (view.model.occupants.length === 5), 1000);
|
||||
|
||||
const modal = await openModtools(_converse, view);
|
||||
let tab = modal.el.querySelector('#affiliations-tab');
|
||||
let tab = modal.querySelector('#affiliations-tab');
|
||||
// Clear so that we don't match older stanzas
|
||||
_converse.connection.IQ_stanzas = [];
|
||||
tab.click();
|
||||
let select = modal.el.querySelector('.select-affiliation');
|
||||
let select = modal.querySelector('.select-affiliation');
|
||||
expect(select.value).toBe('owner');
|
||||
select.value = 'admin';
|
||||
let button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
let button = modal.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
button.click();
|
||||
await u.waitUntil(() => !modal.loading_users_with_affiliation);
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.list-group--users > li').length);
|
||||
let user_els = modal.el.querySelectorAll('.list-group--users > li');
|
||||
await u.waitUntil(() => modal.querySelectorAll('.list-group--users > li').length);
|
||||
let user_els = modal.querySelectorAll('.list-group--users > li');
|
||||
expect(user_els.length).toBe(1);
|
||||
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: wiccarocks@shakespeare.lit');
|
||||
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: wiccan');
|
||||
@ -58,8 +58,8 @@ describe("The groupchat moderator tool", function () {
|
||||
select.value = 'owner';
|
||||
button.click();
|
||||
await u.waitUntil(() => !modal.loading_users_with_affiliation);
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.list-group--users > li').length === 2);
|
||||
user_els = modal.el.querySelectorAll('.list-group--users > li');
|
||||
await u.waitUntil(() => modal.querySelectorAll('.list-group--users > li').length === 2);
|
||||
user_els = modal.querySelectorAll('.list-group--users > li');
|
||||
expect(user_els.length).toBe(2);
|
||||
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: romeo@montague.lit');
|
||||
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: romeo');
|
||||
@ -112,25 +112,25 @@ describe("The groupchat moderator tool", function () {
|
||||
];
|
||||
await mock.returnMemberLists(_converse, muc_jid, members);
|
||||
await u.waitUntil(() => view.model.occupants.pluck('affiliation').filter(o => o === 'owner').length === 1);
|
||||
const alert = modal.el.querySelector('.alert-primary');
|
||||
const alert = modal.querySelector('.alert-primary');
|
||||
expect(alert.textContent.trim()).toBe('Affiliation changed');
|
||||
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.list-group--users > li').length === 1);
|
||||
user_els = modal.el.querySelectorAll('.list-group--users > li');
|
||||
await u.waitUntil(() => modal.querySelectorAll('.list-group--users > li').length === 1);
|
||||
user_els = modal.querySelectorAll('.list-group--users > li');
|
||||
expect(user_els.length).toBe(1);
|
||||
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: romeo@montague.lit');
|
||||
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: romeo');
|
||||
expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
|
||||
|
||||
tab = modal.el.querySelector('#roles-tab');
|
||||
tab.click();
|
||||
select = modal.el.querySelector('.select-role');
|
||||
expect(u.isVisible(select)).toBe(true);
|
||||
modal.querySelector('#roles-tab').click();
|
||||
select = modal.querySelector('.select-role');
|
||||
await u.waitUntil(() => u.isVisible(select));
|
||||
|
||||
expect(select.value).toBe('moderator');
|
||||
button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
|
||||
button = modal.querySelector('.btn-primary[name="users_with_role"]');
|
||||
button.click();
|
||||
|
||||
const roles_panel = modal.el.querySelector('#roles-tabpanel');
|
||||
const roles_panel = modal.querySelector('#roles-tabpanel');
|
||||
await u.waitUntil(() => roles_panel.querySelectorAll('.list-group--users > li').length === 1);
|
||||
select.value = 'participant';
|
||||
button.click();
|
||||
@ -158,35 +158,35 @@ describe("The groupchat moderator tool", function () {
|
||||
// Clear so that we don't match older stanzas
|
||||
_converse.connection.IQ_stanzas = [];
|
||||
const modal = await openModtools(_converse, view);
|
||||
const select = modal.el.querySelector('.select-affiliation');
|
||||
const select = modal.querySelector('.select-affiliation');
|
||||
expect(select.value).toBe('owner');
|
||||
select.value = 'member';
|
||||
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
const button = modal.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
button.click();
|
||||
await u.waitUntil(() => !modal.loading_users_with_affiliation);
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.list-group--users > li').length === 6);
|
||||
await u.waitUntil(() => modal.querySelectorAll('.list-group--users > li').length === 6);
|
||||
|
||||
const nicks = Array.from(modal.el.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
|
||||
const nicks = Array.from(modal.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
|
||||
expect(nicks.join(' ')).toBe('gower juliet romeo thirdwitch wiccan witch');
|
||||
|
||||
const filter = modal.el.querySelector('[name="filter"]');
|
||||
const filter = modal.querySelector('[name="filter"]');
|
||||
expect(filter).not.toBe(null);
|
||||
|
||||
filter.value = 'romeo';
|
||||
u.triggerEvent(filter, "keyup", "KeyboardEvent");
|
||||
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
|
||||
await u.waitUntil(() => ( modal.querySelectorAll('.list-group--users > li').length === 1));
|
||||
|
||||
filter.value = 'r';
|
||||
u.triggerEvent(filter, "keyup", "KeyboardEvent");
|
||||
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 3));
|
||||
await u.waitUntil(() => ( modal.querySelectorAll('.list-group--users > li').length === 3));
|
||||
|
||||
filter.value = 'gower';
|
||||
u.triggerEvent(filter, "keyup", "KeyboardEvent");
|
||||
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
|
||||
await u.waitUntil(() => ( modal.querySelectorAll('.list-group--users > li').length === 1));
|
||||
|
||||
filter.value = 'RoMeO';
|
||||
u.triggerEvent(filter, "keyup", "KeyboardEvent");
|
||||
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
|
||||
await u.waitUntil(() => ( modal.querySelectorAll('.list-group--users > li').length === 1));
|
||||
|
||||
}));
|
||||
|
||||
@ -259,40 +259,40 @@ describe("The groupchat moderator tool", function () {
|
||||
message_form.onKeyDown(enter);
|
||||
|
||||
const modal = await u.waitUntil(() => _converse.api.modal.get('converse-modtools-modal'));
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
const tab = modal.el.querySelector('#roles-tab');
|
||||
const tab = modal.querySelector('#roles-tab');
|
||||
tab.click();
|
||||
|
||||
// Clear so that we don't match older stanzas
|
||||
_converse.connection.IQ_stanzas = [];
|
||||
|
||||
const select = modal.el.querySelector('.select-role');
|
||||
const select = modal.querySelector('.select-role');
|
||||
expect(select.value).toBe('moderator');
|
||||
select.value = 'participant';
|
||||
|
||||
const button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
|
||||
const button = modal.querySelector('.btn-primary[name="users_with_role"]');
|
||||
button.click();
|
||||
await u.waitUntil(() => !modal.loading_users_with_role);
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.list-group--users > li').length === 6);
|
||||
await u.waitUntil(() => modal.querySelectorAll('.list-group--users > li').length === 6);
|
||||
|
||||
const nicks = Array.from(modal.el.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
|
||||
const nicks = Array.from(modal.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
|
||||
expect(nicks.join(' ')).toBe('crone newb nomorenicks oldhag some1 tux');
|
||||
|
||||
const filter = modal.el.querySelector('[name="filter"]');
|
||||
const filter = modal.querySelector('[name="filter"]');
|
||||
expect(filter).not.toBe(null);
|
||||
|
||||
filter.value = 'tux';
|
||||
u.triggerEvent(filter, "keyup", "KeyboardEvent");
|
||||
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
|
||||
await u.waitUntil(() => ( modal.querySelectorAll('.list-group--users > li').length === 1));
|
||||
|
||||
filter.value = 'r';
|
||||
u.triggerEvent(filter, "keyup", "KeyboardEvent");
|
||||
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 2));
|
||||
await u.waitUntil(() => ( modal.querySelectorAll('.list-group--users > li').length === 2));
|
||||
|
||||
filter.value = 'crone';
|
||||
u.triggerEvent(filter, "keyup", "KeyboardEvent");
|
||||
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
|
||||
await u.waitUntil(() => ( modal.querySelectorAll('.list-group--users > li').length === 1));
|
||||
}));
|
||||
|
||||
it("shows an error message if a particular affiliation list may not be retrieved",
|
||||
@ -310,14 +310,14 @@ describe("The groupchat moderator tool", function () {
|
||||
const view = _converse.chatboxviews.get(muc_jid);
|
||||
await u.waitUntil(() => (view.model.occupants.length === 5));
|
||||
const modal = await openModtools(_converse, view);
|
||||
const tab = modal.el.querySelector('#affiliations-tab');
|
||||
const tab = modal.querySelector('#affiliations-tab');
|
||||
// Clear so that we don't match older stanzas
|
||||
_converse.connection.IQ_stanzas = [];
|
||||
const IQ_stanzas = _converse.connection.IQ_stanzas;
|
||||
tab.click();
|
||||
const select = modal.el.querySelector('.select-affiliation');
|
||||
const select = modal.querySelector('.select-affiliation');
|
||||
select.value = 'outcast';
|
||||
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
const button = modal.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
button.click();
|
||||
|
||||
const iq_query = await u.waitUntil(() => _.filter(
|
||||
@ -338,10 +338,10 @@ describe("The groupchat moderator tool", function () {
|
||||
_converse.connection._dataRecv(mock.createRequest(error));
|
||||
await u.waitUntil(() => !modal.loading_users_with_affiliation);
|
||||
|
||||
const alert = await u.waitUntil(() => modal.el.querySelector('.alert'));
|
||||
const alert = await u.waitUntil(() => modal.querySelector('.alert'));
|
||||
expect(alert.textContent.trim()).toBe('Error: not allowed to fetch outcast list for MUC lounge@montague.lit');
|
||||
|
||||
const user_els = modal.el.querySelectorAll('.list-group--users > li');
|
||||
const user_els = modal.querySelectorAll('.list-group--users > li');
|
||||
expect(user_els.length).toBe(1);
|
||||
expect(user_els[0].textContent.trim()).toBe('No users with that affiliation found.');
|
||||
}));
|
||||
@ -361,16 +361,16 @@ describe("The groupchat moderator tool", function () {
|
||||
// Clear so that we don't match older stanzas
|
||||
_converse.connection.IQ_stanzas = [];
|
||||
|
||||
const tab = modal.el.querySelector('#affiliations-tab');
|
||||
const tab = modal.querySelector('#affiliations-tab');
|
||||
tab.click();
|
||||
const select = modal.el.querySelector('.select-affiliation');
|
||||
const select = modal.querySelector('.select-affiliation');
|
||||
select.value = 'member';
|
||||
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
const button = modal.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
button.click();
|
||||
await u.waitUntil(() => !modal.loading_users_with_affiliation);
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.list-group--users > li').length === 1);
|
||||
await u.waitUntil(() => modal.querySelectorAll('.list-group--users > li').length === 1);
|
||||
|
||||
const user_els = modal.el.querySelectorAll('.list-group--users > li');
|
||||
const user_els = modal.querySelectorAll('.list-group--users > li');
|
||||
const toggle = user_els[0].querySelector('.list-group-item:nth-child(3n) .toggle-form');
|
||||
const form = user_els[0].querySelector('.list-group-item:nth-child(3n) .affiliation-form');
|
||||
expect(u.hasClass('hidden', form)).toBeTruthy();
|
||||
@ -422,19 +422,19 @@ describe("The groupchat moderator tool", function () {
|
||||
const view = _converse.chatboxviews.get(muc_jid);
|
||||
await u.waitUntil(() => (view.model.occupants.length === 3));
|
||||
const modal = await openModtools(_converse, view);
|
||||
const tab = modal.el.querySelector('#affiliations-tab');
|
||||
const tab = modal.querySelector('#affiliations-tab');
|
||||
// Clear so that we don't match older stanzas
|
||||
_converse.connection.IQ_stanzas = [];
|
||||
tab.click();
|
||||
const show_affiliation_dropdown = modal.el.querySelector('.select-affiliation');
|
||||
const show_affiliation_dropdown = modal.querySelector('.select-affiliation');
|
||||
show_affiliation_dropdown.value = 'member';
|
||||
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
const button = modal.querySelector('.btn-primary[name="users_with_affiliation"]');
|
||||
button.click();
|
||||
|
||||
await u.waitUntil(() => !modal.loading_users_with_affiliation);
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.list-group--users > li').length === 2);
|
||||
await u.waitUntil(() => modal.querySelectorAll('.list-group--users > li').length === 2);
|
||||
|
||||
const user_els = modal.el.querySelectorAll('.list-group--users > li');
|
||||
const user_els = modal.querySelectorAll('.list-group--users > li');
|
||||
let change_affiliation_dropdown = user_els[0].querySelector('.select-affiliation');
|
||||
expect(Array.from(change_affiliation_dropdown.options).map(o => o.value)).toEqual(['member', 'outcast', 'none']);
|
||||
|
||||
|
@ -12,32 +12,32 @@ describe('The "Groupchats" Add modal', function () {
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
mock.closeControlBox(_converse);
|
||||
const modal = _converse.api.modal.get('add-chatroom-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const modal = _converse.api.modal.get('converse-add-muc-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
let label_name = modal.el.querySelector('label[for="chatroom"]');
|
||||
let label_name = modal.querySelector('label[for="chatroom"]');
|
||||
expect(label_name.textContent.trim()).toBe('Groupchat address:');
|
||||
let name_input = modal.el.querySelector('input[name="chatroom"]');
|
||||
const name_input = modal.querySelector('input[name="chatroom"]');
|
||||
expect(name_input.placeholder).toBe('name@conference.example.org');
|
||||
|
||||
const label_nick = modal.el.querySelector('label[for="nickname"]');
|
||||
const label_nick = modal.querySelector('label[for="nickname"]');
|
||||
expect(label_nick.textContent.trim()).toBe('Nickname:');
|
||||
const nick_input = modal.el.querySelector('input[name="nickname"]');
|
||||
const nick_input = modal.querySelector('input[name="nickname"]');
|
||||
expect(nick_input.value).toBe('');
|
||||
nick_input.value = 'romeo';
|
||||
|
||||
expect(modal.el.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat');
|
||||
expect(modal.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat');
|
||||
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
|
||||
modal.el.querySelector('input[name="chatroom"]').value = 'lounge@muc.montague.lit';
|
||||
modal.el.querySelector('form input[type="submit"]').click();
|
||||
modal.querySelector('input[name="chatroom"]').value = 'lounge@muc.montague.lit';
|
||||
modal.querySelector('form input[type="submit"]').click();
|
||||
await u.waitUntil(() => _converse.chatboxes.length);
|
||||
await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
|
||||
|
||||
roomspanel.model.set('muc_domain', 'muc.example.org');
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
label_name = modal.el.querySelector('label[for="chatroom"]');
|
||||
expect(label_name.textContent.trim()).toBe('Groupchat address:');
|
||||
await u.waitUntil(() => modal.el.querySelector('input[name="chatroom"]')?.placeholder === 'name@muc.example.org');
|
||||
label_name = modal.querySelector('label[for="chatroom"]');
|
||||
expect(label_name.textContent.trim()).toBe('Groupchat name:');
|
||||
await u.waitUntil(() => modal.querySelector('input[name="chatroom"]')?.placeholder === 'name@muc.example.org');
|
||||
})
|
||||
);
|
||||
|
||||
@ -46,31 +46,31 @@ describe('The "Groupchats" Add modal', function () {
|
||||
await mock.openControlBox(_converse);
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
const modal = _converse.api.modal.get('add-chatroom-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
expect(modal.el.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat');
|
||||
const modal = _converse.api.modal.get('converse-add-muc-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
expect(modal.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat');
|
||||
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
|
||||
const label_name = modal.el.querySelector('label[for="chatroom"]');
|
||||
const label_name = modal.querySelector('label[for="chatroom"]');
|
||||
expect(label_name.textContent.trim()).toBe('Groupchat name:');
|
||||
let name_input = modal.el.querySelector('input[name="chatroom"]');
|
||||
let name_input = modal.querySelector('input[name="chatroom"]');
|
||||
expect(name_input.placeholder).toBe('name@muc.example.org');
|
||||
name_input.value = 'lounge';
|
||||
let nick_input = modal.el.querySelector('input[name="nickname"]');
|
||||
let nick_input = modal.querySelector('input[name="nickname"]');
|
||||
nick_input.value = 'max';
|
||||
|
||||
modal.el.querySelector('form input[type="submit"]').click();
|
||||
modal.querySelector('form input[type="submit"]').click();
|
||||
await u.waitUntil(() => _converse.chatboxes.length);
|
||||
await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
|
||||
expect(_converse.chatboxes.models.map(m => m.get('id')).includes('lounge@muc.example.org')).toBe(true);
|
||||
|
||||
// However, you can still open MUCs with different domains
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
name_input = modal.el.querySelector('input[name="chatroom"]');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
name_input = modal.querySelector('input[name="chatroom"]');
|
||||
name_input.value = 'lounge@conference.example.org';
|
||||
nick_input = modal.el.querySelector('input[name="nickname"]');
|
||||
nick_input = modal.querySelector('input[name="nickname"]');
|
||||
nick_input.value = 'max';
|
||||
modal.el.querySelector('form input[type="submit"]').click();
|
||||
modal.querySelector('form input[type="submit"]').click();
|
||||
await u.waitUntil(() => _converse.chatboxes.models.filter(c => c.get('type') === 'chatroom').length === 2);
|
||||
await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 2);
|
||||
expect(_converse.chatboxes.models.map(m => m.get('id')).includes('lounge@conference.example.org')).toBe(
|
||||
@ -87,30 +87,30 @@ describe('The "Groupchats" Add modal', function () {
|
||||
await mock.openControlBox(_converse);
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
const modal = _converse.api.modal.get('add-chatroom-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
expect(modal.el.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat');
|
||||
const modal = _converse.api.modal.get('converse-add-muc-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
expect(modal.querySelector('.modal-title').textContent.trim()).toBe('Enter a new Groupchat');
|
||||
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
|
||||
const label_name = modal.el.querySelector('label[for="chatroom"]');
|
||||
const label_name = modal.querySelector('label[for="chatroom"]');
|
||||
expect(label_name.textContent.trim()).toBe('Groupchat name:');
|
||||
let name_input = modal.el.querySelector('input[name="chatroom"]');
|
||||
let name_input = modal.querySelector('input[name="chatroom"]');
|
||||
expect(name_input.placeholder).toBe('');
|
||||
name_input.value = 'lounge';
|
||||
let nick_input = modal.el.querySelector('input[name="nickname"]');
|
||||
let nick_input = modal.querySelector('input[name="nickname"]');
|
||||
nick_input.value = 'max';
|
||||
modal.el.querySelector('form input[type="submit"]').click();
|
||||
modal.querySelector('form input[type="submit"]').click();
|
||||
await u.waitUntil(() => _converse.chatboxes.length);
|
||||
await u.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
|
||||
expect(_converse.chatboxes.models.map(m => m.get('id')).includes('lounge@muc.example.org')).toBe(true);
|
||||
|
||||
// However, you can still open MUCs with different domains
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
name_input = modal.el.querySelector('input[name="chatroom"]');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
name_input = modal.querySelector('input[name="chatroom"]');
|
||||
name_input.value = 'lounge@conference';
|
||||
nick_input = modal.el.querySelector('input[name="nickname"]');
|
||||
nick_input = modal.querySelector('input[name="nickname"]');
|
||||
nick_input.value = 'max';
|
||||
modal.el.querySelector('form input[type="submit"]').click();
|
||||
modal.querySelector('form input[type="submit"]').click();
|
||||
await u.waitUntil(
|
||||
() => _converse.chatboxes.models.filter(c => c.get('type') === 'chatroom').length === 2
|
||||
);
|
||||
|
@ -10,17 +10,17 @@ describe('The "Groupchats" List modal', function () {
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-list-muc-modal').click();
|
||||
mock.closeControlBox(_converse);
|
||||
const modal = _converse.api.modal.get('muc-list-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const modal = _converse.api.modal.get('converse-muc-list-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
|
||||
|
||||
// See: https://xmpp.org/extensions/xep-0045.html#disco-rooms
|
||||
expect(modal.el.querySelectorAll('.available-chatrooms li').length).toBe(0);
|
||||
expect(modal.querySelectorAll('.available-chatrooms li').length).toBe(0);
|
||||
|
||||
const server_input = modal.el.querySelector('input[name="server"]');
|
||||
const server_input = modal.querySelector('input[name="server"]');
|
||||
expect(server_input.placeholder).toBe('conference.example.org');
|
||||
server_input.value = 'chat.shakespeare.lit';
|
||||
modal.el.querySelector('input[type="submit"]').click();
|
||||
modal.querySelector('input[type="submit"]').click();
|
||||
await u.waitUntil(() => _converse.chatboxes.length);
|
||||
|
||||
const IQ_stanzas = _converse.connection.IQ_stanzas;
|
||||
@ -55,8 +55,8 @@ describe('The "Groupchats" List modal', function () {
|
||||
.c('item', { jid: 'street@chat.shakespeare.lit', name: 'A street' }).nodeTree;
|
||||
_converse.connection._dataRecv(mock.createRequest(iq));
|
||||
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 11);
|
||||
const rooms = modal.el.querySelectorAll('.available-chatrooms li');
|
||||
await u.waitUntil(() => modal.querySelectorAll('.available-chatrooms li').length === 11);
|
||||
const rooms = modal.querySelectorAll('.available-chatrooms li');
|
||||
expect(rooms[0].textContent.trim()).toBe('Groupchats found');
|
||||
expect(rooms[1].textContent.trim()).toBe('A Lonely Heath');
|
||||
expect(rooms[2].textContent.trim()).toBe('A Dark Cave');
|
||||
@ -83,9 +83,9 @@ describe('The "Groupchats" List modal', function () {
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-list-muc-modal').click();
|
||||
mock.closeControlBox(_converse);
|
||||
const modal = _converse.api.modal.get('muc-list-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const server_input = modal.el.querySelector('input[name="server"]');
|
||||
const modal = _converse.api.modal.get('converse-muc-list-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
const server_input = modal.querySelector('input[name="server"]');
|
||||
expect(server_input.value).toBe('muc.example.org');
|
||||
})
|
||||
);
|
||||
@ -99,12 +99,12 @@ describe('The "Groupchats" List modal', function () {
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-list-muc-modal').click();
|
||||
mock.closeControlBox(_converse);
|
||||
const modal = _converse.api.modal.get('muc-list-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const modal = _converse.api.modal.get('converse-muc-list-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
|
||||
|
||||
expect(modal.el.querySelector('input[name="server"]')).toBe(null);
|
||||
expect(modal.el.querySelector('input[type="submit"]')).toBe(null);
|
||||
expect(modal.querySelector('input[name="server"]')).toBe(null);
|
||||
expect(modal.querySelector('input[type="submit"]')).toBe(null);
|
||||
await u.waitUntil(() => _converse.chatboxes.length);
|
||||
const sent_stanza = await u.waitUntil(() =>
|
||||
_converse.connection.sent_stanzas
|
||||
@ -129,8 +129,8 @@ describe('The "Groupchats" List modal', function () {
|
||||
.c('item', { jid: 'forres@chat.shakespeare.lit', name: 'The Palace' }).up();
|
||||
_converse.connection._dataRecv(mock.createRequest(iq));
|
||||
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 4);
|
||||
const rooms = modal.el.querySelectorAll('.available-chatrooms li');
|
||||
await u.waitUntil(() => modal.querySelectorAll('.available-chatrooms li').length === 4);
|
||||
const rooms = modal.querySelectorAll('.available-chatrooms li');
|
||||
expect(rooms[0].textContent.trim()).toBe('Groupchats found');
|
||||
expect(rooms[1].textContent.trim()).toBe('A Lonely Heath');
|
||||
expect(rooms[2].textContent.trim()).toBe('A Dark Cave');
|
||||
|
@ -1332,21 +1332,21 @@ describe("Groupchats", function () {
|
||||
await u.waitUntil(() => view.querySelector('.open-invite-modal'));
|
||||
|
||||
view.querySelector('.open-invite-modal').click();
|
||||
const modal = _converse.api.modal.get('muc-invite-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000)
|
||||
const modal = _converse.api.modal.get('converse-muc-invite-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000)
|
||||
|
||||
expect(modal.el.querySelectorAll('#invitee_jids').length).toBe(1);
|
||||
expect(modal.el.querySelectorAll('textarea').length).toBe(1);
|
||||
expect(modal.querySelectorAll('#invitee_jids').length).toBe(1);
|
||||
expect(modal.querySelectorAll('textarea').length).toBe(1);
|
||||
|
||||
spyOn(view.model, 'directInvite').and.callThrough();
|
||||
|
||||
const input = modal.el.querySelector('#invitee_jids');
|
||||
const input = modal.querySelector('#invitee_jids input');
|
||||
input.value = "Balt";
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
modal.querySelector('input[type="submit"]').click();
|
||||
|
||||
await u.waitUntil(() => modal.el.querySelector('.error'));
|
||||
await u.waitUntil(() => modal.querySelector('.error'));
|
||||
|
||||
const error = modal.el.querySelector('.error');
|
||||
const error = modal.querySelector('.error');
|
||||
expect(error.textContent).toBe('Please enter a valid XMPP address');
|
||||
|
||||
let evt = new Event('input');
|
||||
@ -1354,7 +1354,7 @@ describe("Groupchats", function () {
|
||||
|
||||
let sent_stanza;
|
||||
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
|
||||
const hint = await u.waitUntil(() => modal.el.querySelector('.suggestion-box__results li'));
|
||||
const hint = await u.waitUntil(() => modal.querySelector('.suggestion-box__results li'));
|
||||
expect(input.value).toBe('Balt');
|
||||
expect(hint.textContent.trim()).toBe('Balthasar');
|
||||
|
||||
@ -1362,9 +1362,9 @@ describe("Groupchats", function () {
|
||||
evt.button = 0;
|
||||
hint.dispatchEvent(evt);
|
||||
|
||||
const textarea = modal.el.querySelector('textarea');
|
||||
const textarea = modal.querySelector('textarea');
|
||||
textarea.value = "Please join!";
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
modal.querySelector('input[type="submit"]').click();
|
||||
|
||||
expect(view.model.directInvite).toHaveBeenCalled();
|
||||
expect(Strophe.serialize(sent_stanza)).toBe(
|
||||
@ -1634,10 +1634,10 @@ describe("Groupchats", function () {
|
||||
|
||||
const info_el = view.querySelector(".show-muc-details-modal");
|
||||
info_el.click();
|
||||
let modal = _converse.api.modal.get('muc-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
let modal = _converse.api.modal.get('converse-muc-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
let features_list = modal.el.querySelector('.features-list');
|
||||
let features_list = modal.querySelector('.features-list');
|
||||
let features_shown = features_list.textContent.split('\n').map(s => s.trim()).filter(s => s);
|
||||
|
||||
expect(features_shown.join(' ')).toBe(
|
||||
@ -1661,7 +1661,7 @@ describe("Groupchats", function () {
|
||||
expect(view.model.features.get('unsecured')).toBe(false);
|
||||
await u.waitUntil(() => view.querySelector('.chatbox-title__text').textContent.trim() === 'Room');
|
||||
|
||||
modal.el.querySelector('.close').click();
|
||||
modal.querySelector('.close').click();
|
||||
view.querySelector('.configure-chatroom-button').click();
|
||||
|
||||
const IQs = _converse.connection.IQ_stanzas;
|
||||
@ -1792,10 +1792,10 @@ describe("Groupchats", function () {
|
||||
await u.waitUntil(() => new Promise(success => view.model.features.on('change', success)));
|
||||
|
||||
info_el.click();
|
||||
modal = _converse.api.modal.get('muc-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
modal = _converse.api.modal.get('converse-muc-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
features_list = modal.el.querySelector('.features-list');
|
||||
features_list = modal.querySelector('.features-list');
|
||||
features_shown = features_list.textContent.split('\n').map(s => s.trim()).filter(s => s);
|
||||
expect(features_shown.join(' ')).toBe(
|
||||
'Password protected - This groupchat requires a password before entry '+
|
||||
|
@ -19,17 +19,17 @@ describe("A MUC", function () {
|
||||
const dropdown_item = view.querySelector(".open-nickname-modal");
|
||||
dropdown_item.click();
|
||||
|
||||
const modal = _converse.api.modal.get('change-nickname-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el));
|
||||
const modal = _converse.api.modal.get('converse-muc-nickname-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal));
|
||||
|
||||
const input = modal.el.querySelector('input[name="nick"]');
|
||||
const input = modal.querySelector('input[name="nick"]');
|
||||
expect(input.value).toBe(nick);
|
||||
|
||||
const newnick = 'loverboy';
|
||||
input.value = newnick;
|
||||
modal.el.querySelector('input[type="submit"]')?.click();
|
||||
modal.querySelector('input[type="submit"]')?.click();
|
||||
|
||||
await u.waitUntil(() => !u.isVisible(modal.el));
|
||||
await u.waitUntil(() => !u.isVisible(modal));
|
||||
|
||||
const { sent_stanzas } = _converse.connection;
|
||||
const sent_stanza = sent_stanzas.pop()
|
||||
@ -422,13 +422,13 @@ describe("A MUC", function () {
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
mock.closeControlBox(_converse);
|
||||
const modal = _converse.api.modal.get('add-chatroom-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000)
|
||||
const name_input = modal.el.querySelector('input[name="chatroom"]');
|
||||
const modal = _converse.api.modal.get('converse-add-muc-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000)
|
||||
const name_input = modal.querySelector('input[name="chatroom"]');
|
||||
name_input.value = 'lounge@montague.lit';
|
||||
expect(modal.el.querySelector('label[for="nickname"]')).toBe(null);
|
||||
expect(modal.el.querySelector('input[name="nickname"]')).toBe(null);
|
||||
modal.el.querySelector('form input[type="submit"]').click();
|
||||
expect(modal.querySelector('label[for="nickname"]')).toBe(null);
|
||||
expect(modal.querySelector('input[name="nickname"]')).toBe(null);
|
||||
modal.querySelector('form input[type="submit"]').click();
|
||||
await u.waitUntil(() => _converse.chatboxes.length > 1);
|
||||
const chatroom = _converse.chatboxes.get('lounge@montague.lit');
|
||||
expect(chatroom.get('nick')).toBe('romeo');
|
||||
@ -442,11 +442,11 @@ describe("A MUC", function () {
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
mock.closeControlBox(_converse);
|
||||
const modal = _converse.api.modal.get('add-chatroom-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000)
|
||||
const label_nick = modal.el.querySelector('label[for="nickname"]');
|
||||
const modal = _converse.api.modal.get('converse-add-muc-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000)
|
||||
const label_nick = modal.querySelector('label[for="nickname"]');
|
||||
expect(label_nick.textContent.trim()).toBe('Nickname:');
|
||||
const nick_input = modal.el.querySelector('input[name="nickname"]');
|
||||
const nick_input = modal.querySelector('input[name="nickname"]');
|
||||
expect(nick_input.value).toBe('romeo');
|
||||
}));
|
||||
|
||||
@ -458,11 +458,11 @@ describe("A MUC", function () {
|
||||
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
|
||||
roomspanel.querySelector('.show-add-muc-modal').click();
|
||||
mock.closeControlBox(_converse);
|
||||
const modal = _converse.api.modal.get('add-chatroom-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000)
|
||||
const label_nick = modal.el.querySelector('label[for="nickname"]');
|
||||
const modal = _converse.api.modal.get('converse-add-muc-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000)
|
||||
const label_nick = modal.querySelector('label[for="nickname"]');
|
||||
expect(label_nick.textContent.trim()).toBe('Nickname:');
|
||||
const nick_input = modal.el.querySelector('input[name="nickname"]');
|
||||
const nick_input = modal.querySelector('input[name="nickname"]');
|
||||
expect(nick_input.value).toBe('st.nick');
|
||||
}));
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import ModeratorToolsModal from './modals/moderator-tools.js';
|
||||
import OccupantModal from './modals/occupant.js';
|
||||
import './modals/occupant.js';
|
||||
import './modals/moderator-tools.js';
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_spinner from 'templates/spinner.js';
|
||||
import { __ } from 'i18n';
|
||||
@ -234,19 +234,19 @@ export function showModeratorToolsModal (muc, affiliation) {
|
||||
if (!muc.verifyRoles(['moderator'])) {
|
||||
return;
|
||||
}
|
||||
let modal = api.modal.get(ModeratorToolsModal.id);
|
||||
let modal = api.modal.get('converse-modtools-modal');
|
||||
if (modal) {
|
||||
modal.affiliation = affiliation;
|
||||
modal.render();
|
||||
} else {
|
||||
modal = api.modal.create(ModeratorToolsModal, { affiliation, 'jid': muc.get('jid') });
|
||||
modal = api.modal.create('converse-modtools-modal', { affiliation, 'jid': muc.get('jid') });
|
||||
}
|
||||
modal.show();
|
||||
}
|
||||
|
||||
|
||||
export function showOccupantModal (ev, occupant) {
|
||||
api.modal.show(OccupantModal, { 'model': occupant }, ev);
|
||||
api.modal.show('converse-muc-occupant-modal', { 'model': occupant }, ev);
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
import './fingerprints.js';
|
||||
import './profile.js';
|
||||
import 'modals/user-details.js';
|
||||
import 'shared/modals/user-details.js';
|
||||
import 'plugins/profile/index.js';
|
||||
import ConverseMixins from './mixins/converse.js';
|
||||
import Device from './device.js';
|
||||
|
@ -11,7 +11,7 @@ const fingerprint = (el) => html`
|
||||
const device_with_fingerprint = (el) => {
|
||||
const i18n_fingerprint_checkbox_label = __('Checkbox for selecting the following fingerprint');
|
||||
return html`
|
||||
<li class="fingerprint-removal-item list-group-item nopadding">
|
||||
<li class="fingerprint-removal-item list-group-item">
|
||||
<label>
|
||||
<input type="checkbox" value="${el.device.get('id')}"
|
||||
aria-label="${i18n_fingerprint_checkbox_label}"/>
|
||||
@ -26,7 +26,7 @@ const device_without_fingerprint = (el) => {
|
||||
const i18n_device_without_fingerprint = __('Device without a fingerprint');
|
||||
const i18n_fingerprint_checkbox_label = __('Checkbox for selecting the following device');
|
||||
return html`
|
||||
<li class="fingerprint-removal-item list-group-item nopadding">
|
||||
<li class="fingerprint-removal-item list-group-item">
|
||||
<label>
|
||||
<input type="checkbox" value="${el.device.get('id')}"
|
||||
aria-label="${i18n_fingerprint_checkbox_label}"/>
|
||||
@ -49,7 +49,7 @@ const device_list = (el) => {
|
||||
const i18n_select_all = __('Select all');
|
||||
return html`
|
||||
<ul class="list-group fingerprints">
|
||||
<li class="list-group-item nopadding active">
|
||||
<li class="list-group-item active">
|
||||
<label>
|
||||
<input type="checkbox" class="select-all" @change=${el.selectAll} title="${i18n_select_all}" aria-label="${i18n_other_devices_label}"/>
|
||||
${i18n_other_devices}
|
||||
|
@ -1047,8 +1047,8 @@ describe("The OMEMO module", function() {
|
||||
const view = _converse.chatboxviews.get(contact_jid);
|
||||
const show_modal_button = view.querySelector('.show-user-details-modal');
|
||||
show_modal_button.click();
|
||||
const modal = _converse.api.modal.get('user-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const modal = _converse.api.modal.get('converse-user-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
|
||||
expect(Strophe.serialize(iq_stanza)).toBe(
|
||||
@ -1068,7 +1068,7 @@ describe("The OMEMO module", function() {
|
||||
.c('device', {'id': '555'})
|
||||
));
|
||||
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, contact_jid, '555'));
|
||||
expect(Strophe.serialize(iq_stanza)).toBe(
|
||||
@ -1097,21 +1097,21 @@ describe("The OMEMO module", function() {
|
||||
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'))
|
||||
));
|
||||
|
||||
await u.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
|
||||
expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
|
||||
const el = modal.el.querySelector('.fingerprints .fingerprint');
|
||||
await u.waitUntil(() => modal.querySelectorAll('.fingerprints .fingerprint').length);
|
||||
expect(modal.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
|
||||
const el = modal.querySelector('.fingerprints .fingerprint');
|
||||
expect(el.textContent.trim()).toBe(
|
||||
omemo.formatFingerprint(u.arrayBufferToHex(u.base64ToArrayBuffer('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI')))
|
||||
);
|
||||
expect(modal.el.querySelectorAll('input[type="radio"]').length).toBe(2);
|
||||
expect(modal.querySelectorAll('input[type="radio"]').length).toBe(2);
|
||||
|
||||
const devicelist = _converse.devicelists.get(contact_jid);
|
||||
expect(devicelist.devices.get('555').get('trusted')).toBe(0);
|
||||
|
||||
let trusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="1"]');
|
||||
let trusted_radio = modal.querySelector('input[type="radio"][name="555"][value="1"]');
|
||||
expect(trusted_radio.checked).toBe(true);
|
||||
|
||||
let untrusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="-1"]');
|
||||
let untrusted_radio = modal.querySelector('input[type="radio"][name="555"][value="-1"]');
|
||||
expect(untrusted_radio.checked).toBe(false);
|
||||
|
||||
// Test that the device can be set to untrusted
|
||||
|
@ -3,11 +3,12 @@
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import '../modal/index.js';
|
||||
import './modals/chat-status.js';
|
||||
import './modals/profile.js';
|
||||
import './modals/user-settings.js';
|
||||
import './statusview.js';
|
||||
import '@converse/headless/plugins/status';
|
||||
import '@converse/headless/plugins/vcard';
|
||||
import './modals/chat-status.js';
|
||||
import './modals/profile.js';
|
||||
import { api, converse } from '@converse/headless/core';
|
||||
|
||||
|
||||
|
@ -1,51 +1,37 @@
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import tpl_chat_status_modal from "../templates/chat-status-modal.js";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, converse } from "@converse/headless/core";
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
const ChatStatusModal = BootstrapModal.extend({
|
||||
id: "modal-status-change",
|
||||
events: {
|
||||
"submit form#set-xmpp-status": "onFormSubmitted",
|
||||
"click .clear-input": "clearStatusMessage"
|
||||
},
|
||||
export default class ChatStatusModal extends BaseModal {
|
||||
|
||||
toHTML () {
|
||||
return tpl_chat_status_modal(
|
||||
Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(), {
|
||||
'label_away': __('Away'),
|
||||
'label_busy': __('Busy'),
|
||||
'label_cancel': __('Cancel'),
|
||||
'label_close': __('Close'),
|
||||
'label_custom_status': __('Custom status'),
|
||||
'label_offline': __('Offline'),
|
||||
'label_online': __('Online'),
|
||||
'label_save': __('Save'),
|
||||
'label_xa': __('Away for long'),
|
||||
'modal_title': __('Change chat status'),
|
||||
'placeholder_status_message': __('Personal status message')
|
||||
}));
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
this.el.addEventListener('shown.bs.modal', () => {
|
||||
this.el.querySelector('input[name="status_message"]').focus();
|
||||
initialize () {
|
||||
super.initialize();
|
||||
this.render();
|
||||
this.addEventListener('shown.bs.modal', () => {
|
||||
this.querySelector('input[name="status_message"]').focus();
|
||||
}, false);
|
||||
},
|
||||
}
|
||||
|
||||
renderModal () {
|
||||
return tpl_chat_status_modal(this);
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Change chat status');
|
||||
}
|
||||
|
||||
clearStatusMessage (ev) {
|
||||
if (ev && ev.preventDefault) {
|
||||
ev.preventDefault();
|
||||
u.hideElement(this.el.querySelector('.clear-input'));
|
||||
u.hideElement(this.querySelector('.clear-input'));
|
||||
}
|
||||
const roster_filter = this.el.querySelector('input[name="status_message"]');
|
||||
const roster_filter = this.querySelector('input[name="status_message"]');
|
||||
roster_filter.value = '';
|
||||
},
|
||||
}
|
||||
|
||||
onFormSubmitted (ev) {
|
||||
ev.preventDefault();
|
||||
@ -56,9 +42,8 @@ const ChatStatusModal = BootstrapModal.extend({
|
||||
});
|
||||
this.modal.hide();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
_converse.ChatStatusModal = ChatStatusModal;
|
||||
|
||||
export default ChatStatusModal;
|
||||
api.elements.define('converse-chat-status-modal', ChatStatusModal);
|
||||
|
@ -1,32 +1,27 @@
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import bootstrap from "bootstrap.native";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_profile_modal from "../templates/profile_modal.js";
|
||||
import Compress from 'client-compress';
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
import { _converse, api } from "@converse/headless/core";
|
||||
|
||||
const { sizzle } = converse.env;
|
||||
|
||||
const options = {
|
||||
const compress = new Compress({
|
||||
targetSize: 0.1,
|
||||
quality: 0.75,
|
||||
maxWidth: 256,
|
||||
maxHeight: 256
|
||||
}
|
||||
});
|
||||
|
||||
const compress = new Compress(options)
|
||||
export default class ProfileModal extends BaseModal {
|
||||
|
||||
|
||||
const ProfileModal = BootstrapModal.extend({
|
||||
id: "user-profile-modal",
|
||||
events: {
|
||||
'submit .profile-form': 'onFormSubmitted'
|
||||
},
|
||||
constructor (options) {
|
||||
super(options);
|
||||
this.tab = 'profile';
|
||||
}
|
||||
|
||||
initialize () {
|
||||
super.initialize();
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
/**
|
||||
* Triggered when the _converse.ProfileModal has been created and initialized.
|
||||
* @event _converse#profileModalInitialized
|
||||
@ -34,19 +29,15 @@ const ProfileModal = BootstrapModal.extend({
|
||||
* @example _converse.api.listen.on('profileModalInitialized', status => { ... });
|
||||
*/
|
||||
api.trigger('profileModalInitialized', this.model);
|
||||
},
|
||||
}
|
||||
|
||||
toHTML () {
|
||||
return tpl_profile_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(),
|
||||
{ 'view': this }
|
||||
));
|
||||
},
|
||||
renderModal () {
|
||||
return tpl_profile_modal(this);
|
||||
}
|
||||
|
||||
afterRender () {
|
||||
this.tabs = sizzle('.nav-item .nav-link', this.el).map(e => new bootstrap.Tab(e));
|
||||
},
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Your Profile');
|
||||
}
|
||||
|
||||
async setVCard (data) {
|
||||
try {
|
||||
@ -60,7 +51,7 @@ const ProfileModal = BootstrapModal.extend({
|
||||
return;
|
||||
}
|
||||
this.modal.hide();
|
||||
},
|
||||
}
|
||||
|
||||
onFormSubmitted (ev) {
|
||||
ev.preventDefault();
|
||||
@ -95,8 +86,6 @@ const ProfileModal = BootstrapModal.extend({
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_converse.ProfileModal = ProfileModal;
|
||||
|
||||
export default ProfileModal;
|
||||
api.elements.define('converse-profile-modal', ProfileModal);
|
||||
|
38
src/plugins/profile/modals/styles/profile.scss
Normal file
38
src/plugins/profile/modals/styles/profile.scss
Normal file
@ -0,0 +1,38 @@
|
||||
converse-profile-modal {
|
||||
.profile-form {
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.fingerprint-removal {
|
||||
label {
|
||||
display: flex;
|
||||
padding: 0.75rem 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
font-size: 95%;
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.fingerprints {
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.fingerprint-trust {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 95%;
|
||||
.fingerprint {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +1,41 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { __ } from 'i18n';
|
||||
import { api } from "@converse/headless/core";
|
||||
import { _converse, api } from "@converse/headless/core.js";
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
|
||||
|
||||
const tpl_navigation = (o) => {
|
||||
const tpl_navigation = (el) => {
|
||||
const i18n_about = __('About');
|
||||
const i18n_commands = __('Commands');
|
||||
return html`
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link active" id="about-tab" href="#about-tabpanel" aria-controls="about-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>${i18n_about}</a>
|
||||
<a class="nav-link ${el.tab === "about" ? "active" : ""}"
|
||||
id="about-tab"
|
||||
href="#about-tabpanel"
|
||||
aria-controls="about-tabpanel"
|
||||
role="tab"
|
||||
data-toggle="tab"
|
||||
data-name="about"
|
||||
@click=${ev => el.switchTab(ev)}>${i18n_about}</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link" id="commands-tab" href="#commands-tabpanel" aria-controls="commands-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>${i18n_commands}</a>
|
||||
<a class="nav-link ${el.tab === "commands" ? "active" : ""}"
|
||||
id="commands-tab"
|
||||
href="#commands-tabpanel"
|
||||
aria-controls="commands-tabpanel"
|
||||
role="tab"
|
||||
data-toggle="tab"
|
||||
data-name="commands"
|
||||
@click=${ev => el.switchTab(ev)}>${i18n_commands}</a>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
export default (o) => {
|
||||
const i18n_modal_title = __('Settings');
|
||||
export default (el) => {
|
||||
const first_subtitle = __(
|
||||
'%1$s Open Source %2$s XMPP chat client brought to you by %3$s Opkode %2$s',
|
||||
'<a target="_blank" rel="nofollow" href="https://conversejs.org">',
|
||||
@ -39,38 +51,31 @@ export default (o) => {
|
||||
const show_client_info = api.settings.get('show_client_info');
|
||||
const allow_adhoc_commands = api.settings.get('allow_adhoc_commands');
|
||||
const show_both_tabs = show_client_info && allow_adhoc_commands;
|
||||
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="converse-modtools-modal-label">${i18n_modal_title}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${ show_both_tabs ? tpl_navigation(o) : '' }
|
||||
${ show_both_tabs ? tpl_navigation(el) : '' }
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane tab-pane--columns ${show_client_info ? 'active' : ''}"
|
||||
${ show_client_info ? html`
|
||||
<div class="tab-pane tab-pane--columns ${ el.tab === 'about' ? 'active' : ''}"
|
||||
id="about-tabpanel" role="tabpanel" aria-labelledby="about-tab">
|
||||
|
||||
<span class="modal-alert"></span>
|
||||
<br/>
|
||||
<div class="container">
|
||||
<h6 class="brand-heading">Converse</h6>
|
||||
<p class="brand-subtitle">${o.version_name}</p>
|
||||
<p class="brand-subtitle">${_converse.VERSION_NAME}</p>
|
||||
<p class="brand-subtitle">${unsafeHTML(DOMPurify.sanitize(first_subtitle))}</p>
|
||||
<p class="brand-subtitle">${unsafeHTML(DOMPurify.sanitize(second_subtitle))}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>` : '' }
|
||||
|
||||
<div class="tab-pane tab-pane--columns ${!show_client_info && allow_adhoc_commands ? 'active' : ''}"
|
||||
${ allow_adhoc_commands ? html`
|
||||
<div class="tab-pane tab-pane--columns ${ el.tab === 'commands' ? 'active' : ''}"
|
||||
id="commands-tabpanel"
|
||||
role="tabpanel"
|
||||
aria-labelledby="commands-tab">
|
||||
<converse-adhoc-commands/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> ` : '' }
|
||||
</div>
|
||||
`};
|
||||
|
@ -1,23 +1,31 @@
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import tpl_user_settings_modal from "./templates/user-settings.js";
|
||||
import { __ } from 'i18n';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
let _converse;
|
||||
export default class UserSettingsModal extends BaseModal {
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
id: "converse-client-info-modal",
|
||||
constructor (options) {
|
||||
super(options);
|
||||
|
||||
initialize (settings) {
|
||||
_converse = settings._converse;
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
const show_client_info = api.settings.get('show_client_info');
|
||||
const allow_adhoc_commands = api.settings.get('allow_adhoc_commands');
|
||||
const show_both_tabs = show_client_info && allow_adhoc_commands;
|
||||
|
||||
toHTML () {
|
||||
return tpl_user_settings_modal(
|
||||
Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(),
|
||||
{ 'version_name': _converse.VERSION_NAME }
|
||||
)
|
||||
);
|
||||
if (show_both_tabs || show_client_info) {
|
||||
this.tab = 'about';
|
||||
} else if (allow_adhoc_commands) {
|
||||
this.tab = 'commands';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderModal () {
|
||||
return tpl_user_settings_modal(this);
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Settings');
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-user-settings-modal', UserSettingsModal);
|
||||
|
@ -1,10 +1,8 @@
|
||||
import UserSettingsModal from './modals/user-settings';
|
||||
import tpl_profile from './templates/profile.js';
|
||||
import { CustomElement } from 'shared/components/element.js';
|
||||
import { _converse, api } from '@converse/headless/core';
|
||||
|
||||
class Profile extends CustomElement {
|
||||
|
||||
initialize () {
|
||||
this.model = _converse.xmppstatus;
|
||||
this.listenTo(this.model, "change", () => this.requestUpdate());
|
||||
@ -18,17 +16,17 @@ class Profile extends CustomElement {
|
||||
|
||||
showProfileModal (ev) {
|
||||
ev?.preventDefault();
|
||||
api.modal.show(_converse.ProfileModal, {model: this.model}, ev);
|
||||
api.modal.show('converse-profile-modal', { model: this.model }, ev);
|
||||
}
|
||||
|
||||
showStatusChangeModal (ev) {
|
||||
ev?.preventDefault();
|
||||
api.modal.show(_converse.ChatStatusModal, {model: this.model}, ev);
|
||||
api.modal.show('converse-chat-status-modal', { model: this.model }, ev);
|
||||
}
|
||||
|
||||
showUserSettingsModal(ev) {
|
||||
showUserSettingsModal (ev) {
|
||||
ev?.preventDefault();
|
||||
api.modal.show(UserSettingsModal, {model: this.model, _converse}, ev);
|
||||
api.modal.show('converse-user-settings-modal', { model: this.model, _converse }, ev);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,53 +1,52 @@
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js";
|
||||
import { __ } from 'i18n';
|
||||
|
||||
|
||||
export default (o) => html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="changeStatusModalLabel">${o.modal_title}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
<form class="converse-form set-xmpp-status" id="set-xmpp-status">
|
||||
export default (el) => {
|
||||
const label_away = __('Away');
|
||||
const label_busy = __('Busy');
|
||||
const label_online = __('Online');
|
||||
const label_save = __('Save');
|
||||
const label_xa = __('Away for long');
|
||||
const placeholder_status_message = __('Personal status message');
|
||||
const status = el.model.get('status');
|
||||
const status_message = el.model.get('status_message');
|
||||
|
||||
return html`
|
||||
<form class="converse-form set-xmpp-status" id="set-xmpp-status" @submit=${ev => el.onFormSubmitted(ev)}>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-radio">
|
||||
<input ?checked=${o.status === 'online'}
|
||||
<input ?checked=${status === 'online'}
|
||||
type="radio" id="radio-online" value="online" name="chat_status" class="custom-control-input"/>
|
||||
<label class="custom-control-label" for="radio-online">
|
||||
<converse-icon size="1em" class="fa fa-circle chat-status chat-status--online"></converse-icon>${o.label_online}</label>
|
||||
<converse-icon size="1em" class="fa fa-circle chat-status chat-status--online"></converse-icon>${label_online}</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input ?checked=${o.status === 'busy'}
|
||||
<input ?checked=${status === 'busy'}
|
||||
type="radio" id="radio-busy" value="dnd" name="chat_status" class="custom-control-input"/>
|
||||
<label class="custom-control-label" for="radio-busy">
|
||||
<converse-icon size="1em" class="fa fa-minus-circle chat-status chat-status--busy"></converse-icon>${o.label_busy}</label>
|
||||
<converse-icon size="1em" class="fa fa-minus-circle chat-status chat-status--busy"></converse-icon>${label_busy}</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input ?checked=${o.status === 'away'}
|
||||
<input ?checked=${status === 'away'}
|
||||
type="radio" id="radio-away" value="away" name="chat_status" class="custom-control-input"/>
|
||||
<label class="custom-control-label" for="radio-away">
|
||||
<converse-icon size="1em" class="fa fa-circle chat-status chat-status--away"></converse-icon>${o.label_away}</label>
|
||||
<converse-icon size="1em" class="fa fa-circle chat-status chat-status--away"></converse-icon>${label_away}</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input ?checked=${o.status === 'xa'}
|
||||
<input ?checked=${status === 'xa'}
|
||||
type="radio" id="radio-xa" value="xa" name="chat_status" class="custom-control-input"/>
|
||||
<label class="custom-control-label" for="radio-xa">
|
||||
<converse-icon size="1em" class="far fa-circle chat-status chat-status--xa"></converse-icon>${o.label_xa}</label>
|
||||
<converse-icon size="1em" class="far fa-circle chat-status chat-status--xa"></converse-icon>${label_xa}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="btn-group w-100">
|
||||
<input name="status_message" type="text" class="form-control"
|
||||
value="${o.status_message || ''}" placeholder="${o.placeholder_status_message}"/>
|
||||
<converse-icon size="1em" class="fa fa-times clear-input ${o.status_message ? '' : 'hidden'}"></converse-icon>
|
||||
<input name="status_message" type="text" class="form-control" autofocus
|
||||
value="${status_message || ''}" placeholder="${placeholder_status_message}"/>
|
||||
<converse-icon size="1em" class="fa fa-times clear-input ${status_message ? '' : 'hidden'}" @click=${ev => el.clearStatusMessage(ev)}></converse-icon>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">${o.label_save}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
<button type="submit" class="btn btn-primary">${label_save}</button>
|
||||
</form>`;
|
||||
}
|
||||
|
@ -2,16 +2,40 @@ import "shared/components/image-picker.js";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse } from "@converse/headless/core";
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js";
|
||||
|
||||
const omemo_page = () => html`
|
||||
<div class="tab-pane" id="omemo-tabpanel" role="tabpanel" aria-labelledby="omemo-tab">
|
||||
const omemo_page = (el) => html`
|
||||
<div class="tab-pane ${ el.tab === 'omemo' ? 'active' : ''}" id="omemo-tabpanel" role="tabpanel" aria-labelledby="omemo-tab">
|
||||
<converse-omemo-profile></converse-omemo-profile>
|
||||
</div>`;
|
||||
|
||||
const navigation = (el) => {
|
||||
const i18n_omemo = __('OMEMO');
|
||||
const i18n_profile = __('Profile');
|
||||
|
||||
export default (o) => {
|
||||
const heading_profile = __('Your Profile');
|
||||
return html`<ul class="nav nav-pills justify-content-center">
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link ${el.tab === "profile" ? "active" : ""}"
|
||||
id="profile-tab"
|
||||
href="#profile-tabpanel"
|
||||
aria-controls="profile-tabpanel" role="tab"
|
||||
@click=${ev => el.switchTab(ev)}
|
||||
data-name="profile"
|
||||
data-toggle="tab">${i18n_profile}</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link ${el.tab === "omemo" ? "active" : ""}"
|
||||
id="omemo-tab"
|
||||
href="#omemo-tabpanel"
|
||||
aria-controls="omemo-tabpanel" role="tab"
|
||||
@click=${ev => el.switchTab(ev)}
|
||||
data-name="omemo"
|
||||
data-toggle="tab">${i18n_omemo}</a>
|
||||
</li>
|
||||
</ul>`;
|
||||
}
|
||||
|
||||
export default (el) => {
|
||||
const o = { ...el.model.toJSON(), ...el.model.vcard.toJSON() };
|
||||
const i18n_email = __('Email');
|
||||
const i18n_fullname = __('Full Name');
|
||||
const i18n_jid = __('XMPP Address');
|
||||
@ -20,32 +44,12 @@ export default (o) => {
|
||||
const i18n_save = __('Save and close');
|
||||
const i18n_role_help = __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.');
|
||||
const i18n_url = __('URL');
|
||||
const i18n_omemo = __('OMEMO');
|
||||
const i18n_profile = __('Profile');
|
||||
|
||||
const navigation =
|
||||
html`<ul class="nav nav-pills justify-content-center">
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link active" id="profile-tab" href="#profile-tabpanel" aria-controls="profile-tabpanel" role="tab" data-toggle="tab">${i18n_profile}</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link" id="omemo-tab" href="#omemo-tabpanel" aria-controls="omemo-tabpanel" role="tab" data-toggle="tab">${i18n_omemo}</a>
|
||||
</li>
|
||||
</ul>`;
|
||||
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="user-profile-modal-label">${heading_profile}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
${_converse.pluggable.plugins['converse-omemo']?.enabled(_converse) ? navigation : ''}
|
||||
${_converse.pluggable.plugins['converse-omemo']?.enabled(_converse) ? navigation(el) : ''}
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="profile-tabpanel" role="tabpanel" aria-labelledby="profile-tab">
|
||||
<form class="converse-form converse-form--modal profile-form" action="#">
|
||||
<div class="tab-pane ${ el.tab === 'profile' ? 'active' : ''}" id="profile-tabpanel" role="tabpanel" aria-labelledby="profile-tab">
|
||||
<form class="converse-form converse-form--modal profile-form" action="#" @submit=${ev => el.onFormSubmitted(ev)}>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<converse-image-picker .data="${{image: o.image, image_type: o.image_type}}" width="128" height="128"></converse-image-picker>
|
||||
@ -84,10 +88,7 @@ export default (o) => {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
${ _converse.pluggable.plugins['converse-omemo']?.enabled(_converse) ? omemo_page() : '' }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${ _converse.pluggable.plugins['converse-omemo']?.enabled(_converse) ? omemo_page(el) : '' }
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import AddMUCModal from 'plugins/muc-views/modals/add-muc.js';
|
||||
import MUCListModal from 'plugins/muc-views/modals/muc-list.js';
|
||||
import 'plugins/muc-views/modals/add-muc.js';
|
||||
import 'plugins/muc-views/modals/muc-list.js';
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api } from "@converse/headless/core";
|
||||
import { html } from "lit";
|
||||
@ -80,12 +80,12 @@ export default (o) => {
|
||||
<div class="d-flex controlbox-padded">
|
||||
<span class="w-100 controlbox-heading controlbox-heading--groupchats">${i18n_heading_chatrooms}</span>
|
||||
<a class="controlbox-heading__btn show-list-muc-modal"
|
||||
@click=${(ev) => api.modal.show(MUCListModal, { 'model': o.model }, ev)}
|
||||
@click=${(ev) => api.modal.show('converse-muc-list-modal', { 'model': o.model }, ev)}
|
||||
title="${i18n_title_list_rooms}" data-toggle="modal" data-target="#muc-list-modal">
|
||||
<converse-icon class="fa fa-list-ul right" size="1em"></converse-icon>
|
||||
</a>
|
||||
<a class="controlbox-heading__btn show-add-muc-modal"
|
||||
@click=${(ev) => api.modal.show(AddMUCModal, { 'model': o.model }, ev)}
|
||||
@click=${(ev) => api.modal.show('converse-add-muc-modal', { 'model': o.model }, ev)}
|
||||
title="${i18n_title_new_room}" data-toggle="modal" data-target="#add-chatrooms-modal">
|
||||
<converse-icon class="fa fa-plus right" size="1em"></converse-icon>
|
||||
</a>
|
||||
|
@ -255,9 +255,9 @@ describe("A groupchat shown in the groupchats list", function () {
|
||||
const info_el = rooms_list.querySelector(".room-info");
|
||||
info_el.click();
|
||||
|
||||
const modal = _converse.api.modal.get('muc-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
let els = modal.el.querySelectorAll('p.room-info');
|
||||
const modal = _converse.api.modal.get('converse-muc-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
let els = modal.querySelectorAll('p.room-info');
|
||||
expect(els[0].textContent).toBe("Name: A Dark Cave")
|
||||
|
||||
expect(els[1].querySelector('strong').textContent).toBe("XMPP address");
|
||||
@ -266,7 +266,7 @@ describe("A groupchat shown in the groupchats list", function () {
|
||||
expect(els[2].querySelector('converse-rich-text').textContent).toBe("This is the description");
|
||||
|
||||
expect(els[3].textContent).toBe("Online users: 1")
|
||||
const features_list = modal.el.querySelector('.features-list');
|
||||
const features_list = modal.querySelector('.features-list');
|
||||
expect(features_list.textContent.replace(/(\n|\s{2,})/g, '')).toBe(
|
||||
'Password protected - This groupchat requires a password before entry'+
|
||||
'Hidden - This groupchat is not publicly searchable'+
|
||||
@ -287,11 +287,11 @@ describe("A groupchat shown in the groupchats list", function () {
|
||||
});
|
||||
_converse.connection._dataRecv(mock.createRequest(presence));
|
||||
|
||||
els = modal.el.querySelectorAll('p.room-info');
|
||||
els = modal.querySelectorAll('p.room-info');
|
||||
expect(els[3].textContent).toBe("Online users: 2")
|
||||
|
||||
view.model.set({'subject': {'author': 'someone', 'text': 'Hatching dark plots'}});
|
||||
els = modal.el.querySelectorAll('p.room-info');
|
||||
els = modal.querySelectorAll('p.room-info');
|
||||
expect(els[0].textContent).toBe("Name: A Dark Cave")
|
||||
|
||||
expect(els[1].querySelector('strong').textContent).toBe("XMPP address");
|
||||
|
@ -1,4 +1,4 @@
|
||||
import RoomDetailsModal from 'plugins/muc-views/modals/muc-details.js';
|
||||
import 'plugins/muc-views/modals/muc-details.js';
|
||||
import RoomsListModel from './model.js';
|
||||
import tpl_roomslist from "./templates/roomslist.js";
|
||||
import { CustomElement } from 'shared/components/element.js';
|
||||
@ -58,7 +58,7 @@ export class RoomsList extends CustomElement {
|
||||
const jid = ev.currentTarget.getAttribute('data-room-jid');
|
||||
const room = _converse.chatboxes.get(jid);
|
||||
ev.preventDefault();
|
||||
api.modal.show(RoomDetailsModal, {'model': room}, ev);
|
||||
api.modal.show('converse-muc-details-modal', {'model': room}, ev);
|
||||
}
|
||||
|
||||
async openRoom (ev) { // eslint-disable-line class-methods-use-this
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'shared/autocomplete/index.js';
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import compact from 'lodash-es/compact';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import tpl_add_contact_modal from "./templates/add-contact.js";
|
||||
@ -9,18 +9,22 @@ import { _converse, api, converse } from "@converse/headless/core";
|
||||
const { Strophe } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
const AddContactModal = BootstrapModal.extend({
|
||||
id: "add-contact-modal",
|
||||
export default class AddContactModal extends BaseModal {
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
super.initialize();
|
||||
this.listenTo(this.model, 'change', () => this.render());
|
||||
this.render();
|
||||
this.addEventListener('shown.bs.modal', () => this.querySelector('input[name="jid"]')?.focus(), false);
|
||||
}
|
||||
|
||||
toHTML () {
|
||||
renderModal () {
|
||||
return tpl_add_contact_modal(this);
|
||||
},
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Add a Contact');
|
||||
}
|
||||
|
||||
afterRender () {
|
||||
if (typeof api.settings.get('xhr_user_search_url') === 'string') {
|
||||
@ -28,39 +32,37 @@ const AddContactModal = BootstrapModal.extend({
|
||||
} else {
|
||||
this.initJIDAutoComplete();
|
||||
}
|
||||
const jid_input = this.el.querySelector('input[name="jid"]');
|
||||
this.el.addEventListener('shown.bs.modal', () => jid_input.focus(), false);
|
||||
},
|
||||
}
|
||||
|
||||
initJIDAutoComplete () {
|
||||
if (!api.settings.get('autocomplete_add_contact')) {
|
||||
return;
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__jid').parentElement;
|
||||
const el = this.querySelector('.suggestion-box__jid').parentElement;
|
||||
this.jid_auto_complete = new _converse.AutoComplete(el, {
|
||||
'data': (text, input) => `${input.slice(0, input.indexOf("@"))}@${text}`,
|
||||
'filter': _converse.FILTER_STARTSWITH,
|
||||
'list': [...new Set(_converse.roster.map(item => Strophe.getDomainFromJid(item.get('jid'))))]
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
initGroupAutoComplete () {
|
||||
if (!api.settings.get('autocomplete_add_contact')) {
|
||||
return;
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__jid').parentElement;
|
||||
const el = this.querySelector('.suggestion-box__jid').parentElement;
|
||||
this.jid_auto_complete = new _converse.AutoComplete(el, {
|
||||
'data': (text, input) => `${input.slice(0, input.indexOf("@"))}@${text}`,
|
||||
'filter': _converse.FILTER_STARTSWITH,
|
||||
'list': [...new Set(_converse.roster.map(item => Strophe.getDomainFromJid(item.get('jid'))))]
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
initXHRAutoComplete () {
|
||||
if (!api.settings.get('autocomplete_add_contact')) {
|
||||
return this.initXHRFetch();
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__name').parentElement;
|
||||
const el = this.querySelector('.suggestion-box__name').parentElement;
|
||||
this.name_auto_complete = new _converse.AutoComplete(el, {
|
||||
'auto_evaluate': false,
|
||||
'filter': _converse.FILTER_STARTSWITH,
|
||||
@ -76,16 +78,16 @@ const AddContactModal = BootstrapModal.extend({
|
||||
this.name_auto_complete.evaluate();
|
||||
}
|
||||
};
|
||||
const input_el = this.el.querySelector('input[name="name"]');
|
||||
const input_el = this.querySelector('input[name="name"]');
|
||||
input_el.addEventListener('input', debounce(() => {
|
||||
xhr.open("GET", `${api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(input_el.value)}`, true);
|
||||
xhr.send()
|
||||
} , 300));
|
||||
this.name_auto_complete.on('suggestion-box-selectcomplete', ev => {
|
||||
this.el.querySelector('input[name="name"]').value = ev.text.label;
|
||||
this.el.querySelector('input[name="jid"]').value = ev.text.value;
|
||||
this.querySelector('input[name="name"]').value = ev.text.label;
|
||||
this.querySelector('input[name="jid"]').value = ev.text.value;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
initXHRFetch () {
|
||||
this.xhr = new window.XMLHttpRequest();
|
||||
@ -94,25 +96,25 @@ const AddContactModal = BootstrapModal.extend({
|
||||
const r = this.xhr.responseText;
|
||||
const list = JSON.parse(r).map(i => ({'label': i.fullname || i.jid, 'value': i.jid}));
|
||||
if (list.length !== 1) {
|
||||
const el = this.el.querySelector('.invalid-feedback');
|
||||
const el = this.querySelector('.invalid-feedback');
|
||||
el.textContent = __('Sorry, could not find a contact with that name')
|
||||
u.addClass('d-block', el);
|
||||
return;
|
||||
}
|
||||
const jid = list[0].value;
|
||||
if (this.validateSubmission(jid)) {
|
||||
const form = this.el.querySelector('form');
|
||||
const form = this.querySelector('form');
|
||||
const name = list[0].label;
|
||||
this.afterSubmission(form, jid, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
validateSubmission (jid) {
|
||||
const el = this.el.querySelector('.invalid-feedback');
|
||||
const el = this.querySelector('.invalid-feedback');
|
||||
if (!jid || compact(jid.split('@')).length < 2) {
|
||||
u.addClass('is-invalid', this.el.querySelector('input[name="jid"]'));
|
||||
u.addClass('is-invalid', this.querySelector('input[name="jid"]'));
|
||||
u.addClass('d-block', el);
|
||||
return false;
|
||||
} else if (_converse.roster.get(Strophe.getBareJidFromJid(jid))) {
|
||||
@ -122,16 +124,16 @@ const AddContactModal = BootstrapModal.extend({
|
||||
}
|
||||
u.removeClass('d-block', el);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
|
||||
afterSubmission (form, jid, name, group) {
|
||||
afterSubmission (_form, jid, name, group) {
|
||||
if (group && !Array.isArray(group)) {
|
||||
group = [group];
|
||||
}
|
||||
_converse.roster.addAndSubscribe(jid, name, group);
|
||||
this.model.clear();
|
||||
this.modal.hide();
|
||||
},
|
||||
}
|
||||
|
||||
addContactFromForm (ev) {
|
||||
ev.preventDefault();
|
||||
@ -139,7 +141,7 @@ const AddContactModal = BootstrapModal.extend({
|
||||
const jid = (data.get('jid') || '').trim();
|
||||
|
||||
if (!jid && typeof api.settings.get('xhr_user_search_url') === 'string') {
|
||||
const input_el = this.el.querySelector('input[name="name"]');
|
||||
const input_el = this.querySelector('input[name="name"]');
|
||||
this.xhr.open("GET", `${api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(input_el.value)}`, true);
|
||||
this.xhr.send()
|
||||
return;
|
||||
@ -148,8 +150,6 @@ const AddContactModal = BootstrapModal.extend({
|
||||
this.afterSubmission(ev.target, jid, data.get('name'), data.get('group'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_converse.AddContactModal = AddContactModal;
|
||||
|
||||
export default AddContactModal;
|
||||
api.elements.define('converse-add-contact-modal', AddContactModal);
|
||||
|
@ -2,7 +2,6 @@ import { __ } from 'i18n';
|
||||
import { api } from '@converse/headless/core.js';
|
||||
import { getGroupsAutoCompleteList } from '@converse/headless/plugins/roster/utils.js';
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
|
||||
|
||||
|
||||
export default (el) => {
|
||||
@ -10,16 +9,10 @@ export default (el) => {
|
||||
const i18n_contact_placeholder = __('name@example.org');
|
||||
const i18n_error_message = __('Please enter a valid XMPP address');
|
||||
const i18n_group = __('Group');
|
||||
const i18n_new_contact = __('Add a Contact');
|
||||
const i18n_nickname = __('Name');
|
||||
const i18n_xmpp_address = __('XMPP Address');
|
||||
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addContactModalLabel">${i18n_new_contact}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<form class="converse-form add-xmpp-contact" @submit=${ev => el.addContactFromForm(ev)}>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
@ -44,17 +37,12 @@ export default (el) => {
|
||||
<span class="suggestion-box__additions visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group add-xmpp-contact__group">
|
||||
<label class="clearfix" for="name">${i18n_group}:</label>
|
||||
<converse-autocomplete .list=${getGroupsAutoCompleteList()} name="group"></converse-autocomplete>
|
||||
</div>
|
||||
|
||||
<div class="form-group"><div class="invalid-feedback">${i18n_error_message}</div></div>
|
||||
<button type="submit" class="btn btn-primary">${i18n_add}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
</form>`;
|
||||
}
|
||||
|
@ -13,13 +13,14 @@ export default class RosterView extends CustomElement {
|
||||
|
||||
async initialize () {
|
||||
await api.waitUntil('rosterInitialized')
|
||||
const { presences, roster } = _converse;
|
||||
this.listenTo(_converse, 'rosterContactsFetched', () => this.requestUpdate());
|
||||
this.listenTo(_converse.presences, 'change:show', () => this.requestUpdate());
|
||||
this.listenTo(_converse.roster, 'add', () => this.requestUpdate());
|
||||
this.listenTo(_converse.roster, 'destroy', () => this.requestUpdate());
|
||||
this.listenTo(_converse.roster, 'remove', () => this.requestUpdate());
|
||||
this.listenTo(_converse.roster, 'change', () => this.requestUpdate());
|
||||
this.listenTo(_converse.roster.state, 'change', () => this.requestUpdate());
|
||||
this.listenTo(presences, 'change:show', () => this.requestUpdate());
|
||||
this.listenTo(roster, 'add', () => this.requestUpdate());
|
||||
this.listenTo(roster, 'destroy', () => this.requestUpdate());
|
||||
this.listenTo(roster, 'remove', () => this.requestUpdate());
|
||||
this.listenTo(roster, 'change', () => this.requestUpdate());
|
||||
this.listenTo(roster.state, 'change', () => this.requestUpdate());
|
||||
/**
|
||||
* Triggered once the _converse.RosterView instance has been created and initialized.
|
||||
* @event _converse#rosterViewInitialized
|
||||
@ -42,7 +43,7 @@ export default class RosterView extends CustomElement {
|
||||
}
|
||||
|
||||
showAddContactModal (ev) { // eslint-disable-line class-methods-use-this
|
||||
api.modal.show(_converse.AddContactModal, {'model': new Model()}, ev);
|
||||
api.modal.show('converse-add-contact-modal', {'model': new Model()}, ev);
|
||||
}
|
||||
|
||||
async syncContacts (ev) { // eslint-disable-line class-methods-use-this
|
||||
|
195
src/plugins/rosterview/tests/add-contact-modal.js
Normal file
195
src/plugins/rosterview/tests/add-contact-modal.js
Normal file
@ -0,0 +1,195 @@
|
||||
/*global mock, converse */
|
||||
|
||||
const u = converse.env.utils;
|
||||
const Strophe = converse.env.Strophe;
|
||||
const sizzle = converse.env.sizzle;
|
||||
|
||||
describe("The 'Add Contact' widget", function () {
|
||||
|
||||
it("opens up an add modal when you click on it",
|
||||
mock.initConverse([], {}, async function (_converse) {
|
||||
|
||||
await mock.waitForRoster(_converse, 'all');
|
||||
await mock.openControlBox(_converse);
|
||||
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.querySelector('.add-contact').click()
|
||||
const modal = _converse.api.modal.get('converse-add-contact-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
expect(modal.querySelector('form.add-xmpp-contact')).not.toBe(null);
|
||||
|
||||
const input_jid = modal.querySelector('input[name="jid"]');
|
||||
const input_name = modal.querySelector('input[name="name"]');
|
||||
input_jid.value = 'someone@';
|
||||
|
||||
const evt = new Event('input');
|
||||
input_jid.dispatchEvent(evt);
|
||||
expect(modal.querySelector('.suggestion-box li').textContent).toBe('someone@montague.lit');
|
||||
input_jid.value = 'someone@montague.lit';
|
||||
input_name.value = 'Someone';
|
||||
modal.querySelector('button[type="submit"]').click();
|
||||
|
||||
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
|
||||
expect(Strophe.serialize(sent_stanza)).toEqual(
|
||||
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit" name="Someone"/></query>`+
|
||||
`</iq>`);
|
||||
}));
|
||||
|
||||
it("can be configured to not provide search suggestions",
|
||||
mock.initConverse([], {'autocomplete_add_contact': false}, async function (_converse) {
|
||||
|
||||
await mock.waitForRoster(_converse, 'all', 0);
|
||||
await mock.openControlBox(_converse);
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.querySelector('.add-contact').click()
|
||||
const modal = _converse.api.modal.get('converse-add-contact-modal');
|
||||
expect(modal.jid_auto_complete).toBe(undefined);
|
||||
expect(modal.name_auto_complete).toBe(undefined);
|
||||
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
expect(modal.querySelector('form.add-xmpp-contact')).not.toBe(null);
|
||||
const input_jid = modal.querySelector('input[name="jid"]');
|
||||
input_jid.value = 'someone@montague.lit';
|
||||
modal.querySelector('button[type="submit"]').click();
|
||||
|
||||
const IQ_stanzas = _converse.connection.IQ_stanzas;
|
||||
const sent_stanza = await u.waitUntil(
|
||||
() => IQ_stanzas.filter(s => sizzle(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`, s).length).pop()
|
||||
);
|
||||
expect(Strophe.serialize(sent_stanza)).toEqual(
|
||||
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit"/></query>`+
|
||||
`</iq>`
|
||||
);
|
||||
}));
|
||||
|
||||
it("integrates with xhr_user_search_url to search for contacts",
|
||||
mock.initConverse([], { 'xhr_user_search_url': 'http://example.org/?' },
|
||||
async function (_converse) {
|
||||
|
||||
await mock.waitForRoster(_converse, 'all', 0);
|
||||
|
||||
class MockXHR extends XMLHttpRequest {
|
||||
open () {} // eslint-disable-line
|
||||
responseText = ''
|
||||
send () {
|
||||
this.responseText = JSON.stringify([
|
||||
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
|
||||
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
|
||||
]);
|
||||
this.onload();
|
||||
}
|
||||
}
|
||||
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('converse-add-contact-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
// We only have autocomplete for the name input
|
||||
expect(modal.jid_auto_complete).toBe(undefined);
|
||||
expect(modal.name_auto_complete instanceof _converse.AutoComplete).toBe(true);
|
||||
|
||||
const input_el = modal.querySelector('input[name="name"]');
|
||||
input_el.value = 'marty';
|
||||
input_el.dispatchEvent(new Event('input'));
|
||||
await u.waitUntil(() => modal.querySelector('.suggestion-box li'), 1000);
|
||||
expect(modal.querySelectorAll('.suggestion-box li').length).toBe(1);
|
||||
const suggestion = modal.querySelector('.suggestion-box li');
|
||||
expect(suggestion.textContent).toBe('Marty McFly');
|
||||
|
||||
// Mock selection
|
||||
modal.name_auto_complete.select(suggestion);
|
||||
|
||||
expect(input_el.value).toBe('Marty McFly');
|
||||
expect(modal.querySelector('input[name="jid"]').value).toBe('marty@mcfly.net');
|
||||
modal.querySelector('button[type="submit"]').click();
|
||||
|
||||
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
|
||||
expect(Strophe.serialize(sent_stanza)).toEqual(
|
||||
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
|
||||
`</iq>`);
|
||||
window.XMLHttpRequest = XMLHttpRequestBackup;
|
||||
}));
|
||||
|
||||
it("can be configured to not provide search suggestions for XHR search results",
|
||||
mock.initConverse([],
|
||||
{ 'autocomplete_add_contact': false,
|
||||
'xhr_user_search_url': 'http://example.org/?' },
|
||||
async function (_converse) {
|
||||
|
||||
await mock.waitForRoster(_converse, 'all');
|
||||
await mock.openControlBox(_converse);
|
||||
|
||||
class MockXHR extends XMLHttpRequest {
|
||||
open () {} // eslint-disable-line
|
||||
responseText = ''
|
||||
send () {
|
||||
const value = modal.querySelector('input[name="name"]').value;
|
||||
if (value === 'existing') {
|
||||
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
|
||||
this.responseText = JSON.stringify([{"jid": contact_jid, "fullname": mock.cur_names[0]}]);
|
||||
} else if (value === 'romeo') {
|
||||
this.responseText = JSON.stringify([{"jid": "romeo@montague.lit", "fullname": "Romeo Montague"}]);
|
||||
} else if (value === 'ambiguous') {
|
||||
this.responseText = JSON.stringify([
|
||||
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
|
||||
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
|
||||
]);
|
||||
} else if (value === 'insufficient') {
|
||||
this.responseText = JSON.stringify([]);
|
||||
} else {
|
||||
this.responseText = JSON.stringify([{"jid": "marty@mcfly.net", "fullname": "Marty McFly"}]);
|
||||
}
|
||||
this.onload();
|
||||
}
|
||||
}
|
||||
|
||||
const XMLHttpRequestBackup = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXHR;
|
||||
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.querySelector('.add-contact').click()
|
||||
const modal = _converse.api.modal.get('converse-add-contact-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
|
||||
expect(modal.jid_auto_complete).toBe(undefined);
|
||||
expect(modal.name_auto_complete).toBe(undefined);
|
||||
|
||||
const input_el = modal.querySelector('input[name="name"]');
|
||||
input_el.value = 'ambiguous';
|
||||
modal.querySelector('button[type="submit"]').click();
|
||||
let feedback_el = modal.querySelector('.invalid-feedback');
|
||||
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
|
||||
feedback_el.textContent = '';
|
||||
|
||||
input_el.value = 'insufficient';
|
||||
modal.querySelector('button[type="submit"]').click();
|
||||
feedback_el = modal.querySelector('.invalid-feedback');
|
||||
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
|
||||
feedback_el.textContent = '';
|
||||
|
||||
input_el.value = 'existing';
|
||||
modal.querySelector('button[type="submit"]').click();
|
||||
feedback_el = modal.querySelector('.invalid-feedback');
|
||||
expect(feedback_el.textContent).toBe('This contact has already been added');
|
||||
|
||||
input_el.value = 'Marty McFly';
|
||||
modal.querySelector('button[type="submit"]').click();
|
||||
|
||||
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
|
||||
expect(Strophe.serialize(sent_stanza)).toEqual(
|
||||
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
|
||||
`</iq>`);
|
||||
window.XMLHttpRequest = XMLHttpRequestBackup;
|
||||
}));
|
||||
});
|
@ -17,11 +17,11 @@ describe("A sent presence stanza", function () {
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
const change_status_el = await u.waitUntil(() => cbview.querySelector('.change-status'));
|
||||
change_status_el.click()
|
||||
let modal = _converse.api.modal.get('modal-status-change');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
let modal = _converse.api.modal.get('converse-chat-status-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
const msg = 'My custom status';
|
||||
modal.el.querySelector('input[name="status_message"]').value = msg;
|
||||
modal.el.querySelector('[type="submit"]').click();
|
||||
modal.querySelector('input[name="status_message"]').value = msg;
|
||||
modal.querySelector('[type="submit"]').click();
|
||||
|
||||
const sent_stanzas = _converse.connection.sent_stanzas;
|
||||
let sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
|
||||
@ -31,14 +31,15 @@ describe("A sent presence stanza", function () {
|
||||
`<priority>0</priority>`+
|
||||
`<c hash="sha-1" node="https://conversejs.org" ver="TfHz9vOOfqIG0Z9lW5CuPaWGnrQ=" xmlns="http://jabber.org/protocol/caps"/>`+
|
||||
`</presence>`)
|
||||
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true");
|
||||
await u.waitUntil(() => !u.isVisible(modal.el));
|
||||
await u.waitUntil(() => modal.getAttribute('aria-hidden') === "true");
|
||||
await u.waitUntil(() => !u.isVisible(modal));
|
||||
|
||||
cbview.querySelector('.change-status').click()
|
||||
modal = _converse.api.modal.get('modal-status-change');
|
||||
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('[type="submit"]').click();
|
||||
modal = _converse.api.modal.get('converse-chat-status-modal');
|
||||
await u.waitUntil(() => modal.getAttribute('aria-hidden') === "false", 1000);
|
||||
modal.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
|
||||
modal.querySelector('[type="submit"]').click();
|
||||
|
||||
await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).length === 2);
|
||||
sent_presence = sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop();
|
||||
expect(Strophe.serialize(sent_presence))
|
||||
|
@ -55,12 +55,12 @@ describe("The Protocol", function () {
|
||||
spyOn(_converse.api.vcard, "get").and.callThrough();
|
||||
|
||||
cbview.querySelector('.add-contact').click()
|
||||
const modal = _converse.api.modal.get('add-contact-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const modal = _converse.api.modal.get('converse-add-contact-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
modal.delegateEvents();
|
||||
|
||||
// Fill in the form and submit
|
||||
const form = modal.el.querySelector('form.add-xmpp-contact');
|
||||
const form = modal.querySelector('form.add-xmpp-contact');
|
||||
form.querySelector('input[name="jid"]').value = 'contact@example.org';
|
||||
form.querySelector('input[name="name"]').value = 'Chris Contact';
|
||||
form.querySelector('input[name="group"]').value = 'My Buddies';
|
||||
|
@ -1,5 +1,19 @@
|
||||
import log from "@converse/headless/log";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api } from "@converse/headless/core";
|
||||
|
||||
export 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())
|
||||
]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function highlightRosterItem (chatbox) {
|
||||
_converse.roster?.get(chatbox.get('jid'))?.trigger('highlight');
|
||||
|
@ -55,6 +55,7 @@ export default class AutoCompleteComponent extends CustomElement {
|
||||
'name': { type: String },
|
||||
'placeholder': { type: String },
|
||||
'triggers': { type: String },
|
||||
'required': { type: Boolean },
|
||||
};
|
||||
}
|
||||
|
||||
@ -78,6 +79,7 @@ export default class AutoCompleteComponent extends CustomElement {
|
||||
<ul class="suggestion-box__results ${position_class}" hidden=""></ul>
|
||||
<input
|
||||
?autofocus=${this.autofocus}
|
||||
?required=${this.required}
|
||||
type="text"
|
||||
name="${this.name}"
|
||||
autocomplete="off"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'shared/registry.js';
|
||||
import ImageModal from 'modals/image.js';
|
||||
import ImageModal from 'shared/modals/image.js';
|
||||
import renderRichText from 'shared/directives/rich-text.js';
|
||||
import { CustomElement } from 'shared/components/element.js';
|
||||
import { api } from "@converse/headless/core";
|
||||
@ -31,7 +31,7 @@ export default class MessageBody extends CustomElement {
|
||||
|
||||
onImgClick (ev) { // eslint-disable-line class-methods-use-this
|
||||
ev.preventDefault();
|
||||
api.modal.create(ImageModal, {'src': ev.target.src}, ev).show(ev);
|
||||
api.modal.show('converse-image-modal', {'src': ev.target.src}, ev);
|
||||
}
|
||||
|
||||
onImgLoad () {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import './message-actions.js';
|
||||
import './message-body.js';
|
||||
import 'shared/components/dropdown.js';
|
||||
import 'shared/modals/message-versions.js';
|
||||
import 'shared/modals/user-details.js';
|
||||
import 'shared/registry';
|
||||
import 'plugins/muc-views/modals/occupant.js';
|
||||
import tpl_file_progress from './templates/file-progress.js';
|
||||
import MessageVersionsModal from 'modals/message-versions.js';
|
||||
import OccupantModal from 'plugins/muc-views/modals/occupant.js';
|
||||
import UserDetailsModal from 'modals/user-details.js';
|
||||
import log from '@converse/headless/log';
|
||||
import tpl_info_message from './templates/info-message.js';
|
||||
import tpl_mep_message from 'plugins/muc-views/templates/mep-message.js';
|
||||
@ -214,20 +214,20 @@ export default class Message extends CustomElement {
|
||||
|
||||
showUserModal (ev) {
|
||||
if (this.model.get('sender') === 'me') {
|
||||
api.modal.show(_converse.ProfileModal, {model: this.model}, ev);
|
||||
api.modal.show('converse-profile-modal', {model: this.model}, ev);
|
||||
} else if (this.model.get('type') === 'groupchat') {
|
||||
ev.preventDefault();
|
||||
api.modal.show(OccupantModal, { 'model': this.model.occupant, 'message': this.model }, ev);
|
||||
api.modal.show('converse-muc-occupant-modal', { 'model': this.model.occupant, 'message': this.model }, ev);
|
||||
} else {
|
||||
ev.preventDefault();
|
||||
const chatbox = this.model.collection.chatbox;
|
||||
api.modal.show(UserDetailsModal, { model: chatbox }, ev);
|
||||
api.modal.show('converse-user-details-modal', { model: chatbox }, ev);
|
||||
}
|
||||
}
|
||||
|
||||
showMessageVersionsModal (ev) {
|
||||
ev.preventDefault();
|
||||
api.modal.show(MessageVersionsModal, {'model': this.model}, ev);
|
||||
api.modal.show('converse-message-versions-modal', {'model': this.model}, ev);
|
||||
}
|
||||
|
||||
toggleSpoilerMessage (ev) {
|
||||
|
22
src/shared/modals/image.js
Normal file
22
src/shared/modals/image.js
Normal file
@ -0,0 +1,22 @@
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import tpl_image_modal from "./templates/image.js";
|
||||
import { __ } from 'i18n';
|
||||
import { api } from "@converse/headless/core";
|
||||
import { getFileName } from 'utils/html.js';
|
||||
import { html } from "lit";
|
||||
|
||||
import './styles/image.scss';
|
||||
|
||||
|
||||
export default class ImageModal extends BaseModal {
|
||||
|
||||
renderModal () {
|
||||
return tpl_image_modal({ 'src': this.src });
|
||||
}
|
||||
|
||||
getModalTitle () {
|
||||
return html`${__('Image: ')}<a target="_blank" rel="noopener" href="${this.src}">${getFileName(this.src)}</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-image-modal', ImageModal);
|
19
src/shared/modals/message-versions.js
Normal file
19
src/shared/modals/message-versions.js
Normal file
@ -0,0 +1,19 @@
|
||||
import 'shared/components/message-versions.js';
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import {api } from "@converse/headless/core";
|
||||
|
||||
|
||||
export default class MessageVersionsModal extends BaseModal {
|
||||
|
||||
renderModal () {
|
||||
return html`<converse-message-versions .model=${this.model}></converse-message-versions>`;
|
||||
}
|
||||
|
||||
getModalTitle () { // eslint-disable-line class-methods-use-this
|
||||
return __('Message versions');
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-message-versions-modal', MessageVersionsModal);
|
6
src/shared/modals/styles/image.scss
Normal file
6
src/shared/modals/styles/image.scss
Normal file
@ -0,0 +1,6 @@
|
||||
converse-image-modal {
|
||||
.chat-image--modal {
|
||||
max-height: 99%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
3
src/shared/modals/templates/image.js
Normal file
3
src/shared/modals/templates/image.js
Normal file
@ -0,0 +1,3 @@
|
||||
import { html } from "lit";
|
||||
|
||||
export default (o) => html`<img class="chat-image chat-image--modal" src="${o.src}">`;
|
69
src/shared/modals/templates/user-details.js
Normal file
69
src/shared/modals/templates/user-details.js
Normal file
@ -0,0 +1,69 @@
|
||||
import avatar from 'shared/avatar/templates/avatar.js';
|
||||
import { __ } from 'i18n';
|
||||
import { html } from 'lit';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
const remove_button = (el) => {
|
||||
const i18n_remove_contact = __('Remove as contact');
|
||||
return html`
|
||||
<button type="button" @click="${ev => el.removeContact(ev)}" class="btn btn-danger remove-contact">
|
||||
<converse-icon
|
||||
class="fas fa-trash-alt"
|
||||
color="var(--text-color-lighten-15-percent)"
|
||||
size="1em"
|
||||
></converse-icon>
|
||||
${i18n_remove_contact}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
export const tpl_footer = (el) => {
|
||||
const is_roster_contact = el.model.contact !== undefined;
|
||||
const i18n_refresh = __('Refresh');
|
||||
const allow_contact_removal = api.settings.get('allow_contact_removal');
|
||||
return html`
|
||||
<button type="button" class="btn btn-info refresh-contact" @click=${ev => el.refreshContact(ev)}>
|
||||
<converse-icon
|
||||
class="fa fa-refresh"
|
||||
color="var(--text-color-lighten-15-percent)"
|
||||
size="1em"
|
||||
></converse-icon>
|
||||
${i18n_refresh}</button>
|
||||
${ (allow_contact_removal && is_roster_contact) ? remove_button(el) : '' }
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
export const tpl_user_details_modal = (el) => {
|
||||
const vcard = el.model?.vcard;
|
||||
const vcard_json = vcard ? vcard.toJSON() : {};
|
||||
const o = { ...el.model.toJSON(), ...vcard_json };
|
||||
|
||||
const i18n_address = __('XMPP Address');
|
||||
const i18n_email = __('Email');
|
||||
const i18n_full_name = __('Full Name');
|
||||
const i18n_nickname = __('Nickname');
|
||||
const i18n_profile = __('The User\'s Profile Image');
|
||||
const i18n_role = __('Role');
|
||||
const i18n_url = __('URL');
|
||||
const avatar_data = {
|
||||
'alt_text': i18n_profile,
|
||||
'extra_classes': 'mb-3',
|
||||
'height': '120',
|
||||
'width': '120'
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="modal-body">
|
||||
${ o.image ? html`<div class="mb-4">${avatar(Object.assign(o, avatar_data))}</div>` : '' }
|
||||
${ o.fullname ? html`<p><label>${i18n_full_name}:</label> ${o.fullname}</p>` : '' }
|
||||
<p><label>${i18n_address}:</label> <a href="xmpp:${o.jid}">${o.jid}</a></p>
|
||||
${ o.nickname ? html`<p><label>${i18n_nickname}:</label> ${o.nickname}</p>` : '' }
|
||||
${ o.url ? html`<p><label>${i18n_url}:</label> <a target="_blank" rel="noopener" href="${o.url}">${o.url}</a></p>` : '' }
|
||||
${ o.email ? html`<p><label>${i18n_email}:</label> <a href="mailto:${o.email}">${o.email}</a></p>` : '' }
|
||||
${ o.role ? html`<p><label>${i18n_role}:</label> ${o.role}</p>` : '' }
|
||||
|
||||
<converse-omemo-fingerprints jid=${o.jid}></converse-omemo-fingerprints>
|
||||
</div>
|
||||
`;
|
||||
}
|
@ -17,19 +17,19 @@ describe("The User Details Modal", function () {
|
||||
const view = _converse.chatboxviews.get(contact_jid);
|
||||
let show_modal_button = view.querySelector('.show-user-details-modal');
|
||||
show_modal_button.click();
|
||||
const modal = _converse.api.modal.get('user-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const modal = _converse.api.modal.get('converse-user-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1000);
|
||||
spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
|
||||
|
||||
spyOn(view.model.contact, 'removeFromRoster').and.callFake(callback => callback());
|
||||
let remove_contact_button = modal.el.querySelector('button.remove-contact');
|
||||
let remove_contact_button = modal.querySelector('button.remove-contact');
|
||||
expect(u.isVisible(remove_contact_button)).toBeTruthy();
|
||||
remove_contact_button.click();
|
||||
await u.waitUntil(() => modal.el.getAttribute('aria-hidden'), 1000);
|
||||
await u.waitUntil(() => !u.isVisible(modal.el));
|
||||
await u.waitUntil(() => modal.getAttribute('aria-hidden'), 1000);
|
||||
await u.waitUntil(() => !u.isVisible(modal));
|
||||
show_modal_button = view.querySelector('.show-user-details-modal');
|
||||
show_modal_button.click();
|
||||
remove_contact_button = modal.el.querySelector('button.remove-contact');
|
||||
remove_contact_button = modal.querySelector('button.remove-contact');
|
||||
expect(remove_contact_button === null).toBeTruthy();
|
||||
}));
|
||||
|
||||
@ -44,15 +44,15 @@ describe("The User Details Modal", function () {
|
||||
const view = _converse.chatboxviews.get(contact_jid);
|
||||
let show_modal_button = view.querySelector('.show-user-details-modal');
|
||||
show_modal_button.click();
|
||||
let modal = _converse.api.modal.get('user-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 2000);
|
||||
let modal = _converse.api.modal.get('converse-user-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 2000);
|
||||
spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
|
||||
|
||||
spyOn(view.model.contact, 'removeFromRoster').and.callFake((callback, errback) => errback());
|
||||
let remove_contact_button = modal.el.querySelector('button.remove-contact');
|
||||
let remove_contact_button = modal.querySelector('button.remove-contact');
|
||||
expect(u.isVisible(remove_contact_button)).toBeTruthy();
|
||||
remove_contact_button.click();
|
||||
await u.waitUntil(() => !u.isVisible(modal.el))
|
||||
await u.waitUntil(() => !u.isVisible(modal))
|
||||
await u.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
|
||||
|
||||
const header = document.querySelector('.alert-danger .modal-title');
|
||||
@ -62,14 +62,14 @@ describe("The User Details Modal", function () {
|
||||
document.querySelector('.alert-danger button.close').click();
|
||||
show_modal_button = view.querySelector('.show-user-details-modal');
|
||||
show_modal_button.click();
|
||||
modal = _converse.api.modal.get('user-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 2000)
|
||||
modal = _converse.api.modal.get('converse-user-details-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 2000)
|
||||
|
||||
show_modal_button = view.querySelector('.show-user-details-modal');
|
||||
show_modal_button.click();
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 2000)
|
||||
await u.waitUntil(() => u.isVisible(modal), 2000)
|
||||
|
||||
remove_contact_button = modal.el.querySelector('button.remove-contact');
|
||||
remove_contact_button = modal.querySelector('button.remove-contact');
|
||||
expect(u.isVisible(remove_contact_button)).toBeTruthy();
|
||||
}));
|
||||
});
|
@ -1,36 +1,17 @@
|
||||
import BootstrapModal from "plugins/modal/base.js";
|
||||
import BaseModal from "plugins/modal/modal.js";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_user_details_modal from "./templates/user-details.js";
|
||||
import { tpl_user_details_modal, tpl_footer } from "./templates/user-details.js";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
import { api, converse } from "@converse/headless/core";
|
||||
import { removeContact } from 'plugins/rosterview/utils.js';
|
||||
|
||||
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({
|
||||
id: 'user-details-modal',
|
||||
persistent: true,
|
||||
|
||||
events: {
|
||||
'click button.refresh-contact': 'refreshContact',
|
||||
},
|
||||
export default class UserDetailsModal extends BaseModal {
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
super.initialize();
|
||||
this.model.rosterContactAdded.then(() => this.registerContactEventHandlers());
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.registerContactEventHandlers();
|
||||
@ -41,23 +22,19 @@ const UserDetailsModal = BootstrapModal.extend({
|
||||
* @example _converse.api.listen.on('userDetailsModalInitialized', (chatbox) => { ... });
|
||||
*/
|
||||
api.trigger('userDetailsModalInitialized', this.model);
|
||||
},
|
||||
}
|
||||
|
||||
toHTML () {
|
||||
const vcard = this.model?.vcard;
|
||||
const vcard_json = vcard ? vcard.toJSON() : {};
|
||||
return tpl_user_details_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
vcard_json, {
|
||||
'_converse': _converse,
|
||||
'allow_contact_removal': api.settings.get('allow_contact_removal'),
|
||||
'display_name': this.model.getDisplayName(),
|
||||
'is_roster_contact': this.model.contact !== undefined,
|
||||
'removeContact': ev => this.removeContact(ev),
|
||||
'view': this,
|
||||
'utils': u
|
||||
}));
|
||||
},
|
||||
renderModal () {
|
||||
return tpl_user_details_modal(this);
|
||||
}
|
||||
|
||||
renderModalFooter () {
|
||||
return tpl_footer(this);
|
||||
}
|
||||
|
||||
getModalTitle () {
|
||||
return this.model.getDisplayName();
|
||||
}
|
||||
|
||||
registerContactEventHandlers () {
|
||||
if (this.model.contact !== undefined) {
|
||||
@ -68,7 +45,7 @@ const UserDetailsModal = BootstrapModal.extend({
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
async refreshContact (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
@ -81,7 +58,7 @@ const UserDetailsModal = BootstrapModal.extend({
|
||||
this.alert(__('Sorry, something went wrong while trying to refresh'), 'danger');
|
||||
}
|
||||
u.removeClass('fa-spin', refresh_icon);
|
||||
},
|
||||
}
|
||||
|
||||
async removeContact (ev) {
|
||||
ev?.preventDefault?.();
|
||||
@ -94,9 +71,7 @@ const UserDetailsModal = BootstrapModal.extend({
|
||||
setTimeout(() => removeContact(this.model.contact), 1);
|
||||
this.modal.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_converse.UserDetailsModal = UserDetailsModal;
|
||||
|
||||
export default UserDetailsModal;
|
||||
api.elements.define('converse-user-details-modal', UserDetailsModal);
|
@ -140,13 +140,13 @@ async function openChatRoomViaModal (_converse, jid, nick='') {
|
||||
await openControlBox(_converse);
|
||||
document.querySelector('converse-rooms-list .show-add-muc-modal').click();
|
||||
closeControlBox(_converse);
|
||||
const modal = _converse.api.modal.get('add-chatroom-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1500)
|
||||
modal.el.querySelector('input[name="chatroom"]').value = jid;
|
||||
const modal = _converse.api.modal.get('converse-add-muc-modal');
|
||||
await u.waitUntil(() => u.isVisible(modal), 1500)
|
||||
modal.querySelector('input[name="chatroom"]').value = jid;
|
||||
if (nick) {
|
||||
modal.el.querySelector('input[name="nickname"]').value = nick;
|
||||
modal.querySelector('input[name="nickname"]').value = nick;
|
||||
}
|
||||
modal.el.querySelector('form input[type="submit"]').click();
|
||||
modal.querySelector('form input[type="submit"]').click();
|
||||
await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
|
||||
return _converse.chatboxviews.get(jid);
|
||||
}
|
||||
|
@ -74,7 +74,8 @@ function slideOutWrapup (el) {
|
||||
el.style.height = '';
|
||||
}
|
||||
|
||||
function getFileName (uri) {
|
||||
export function getFileName (url) {
|
||||
const uri = getURI(url);
|
||||
try {
|
||||
return decodeURI(uri.filename());
|
||||
} catch (error) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
<script src="3rdparty/libsignal-protocol.js"></script>
|
||||
<link rel="manifest" href="./manifest.json">
|
||||
<link rel="shortcut icon" type="image/ico" href="favicon.ico"/>
|
||||
<script src="https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"></script>
|
||||
</head>
|
||||
<body class="reset"></body>
|
||||
<script>
|
||||
|
Loading…
Reference in New Issue
Block a user