Commit 9fb20567 authored by JC Brand's avatar JC Brand

Move MUC invite widget into a modal

parent c6ac03e9
...@@ -16094,8 +16094,8 @@ ...@@ -16094,8 +16094,8 @@
"dev": true "dev": true
}, },
"skeletor.js": { "skeletor.js": {
"version": "github:skeletorjs/skeletor#29a6d8f707076e865133b8f36f07c76ba4b4b582", "version": "github:skeletorjs/skeletor#9a4487496bd2810b2f0847acbca136333cf9cfb0",
"from": "github:skeletorjs/skeletor#29a6d8f707076e865133b8f36f07c76ba4b4b582", "from": "github:skeletorjs/skeletor#9a4487496bd2810b2f0847acbca136333cf9cfb0",
"requires": { "requires": {
"lodash": "^4.17.14" "lodash": "^4.17.14"
} }
......
...@@ -172,12 +172,25 @@ ...@@ -172,12 +172,25 @@
.hide-occupants { .hide-occupants {
align-self: flex-end; align-self: flex-end;
cursor: pointer; cursor: pointer;
font-size: var(--font-size-small);
}
}
.occupants-header--title {
margin-top: 0.5em;
margin-bottom: 0.5em;
display: flex;
flex-direction: row;
.fa-user-plus {
margin-top: 0.2em;
} }
} }
.occupants-heading { .occupants-heading {
font-family: var(--heading-font); font-family: var(--heading-font);
margin-bottom: 0.5em; color: var(--chatroom-head-color);
padding-left: 0; padding-left: 0;
margin-right: 1em;
} }
.chatroom-features { .chatroom-features {
display: var(--occupants-features-display); display: var(--occupants-features-display);
......
...@@ -1940,36 +1940,54 @@ ...@@ -1940,36 +1940,54 @@
'muc_anonymous' 'muc_anonymous'
] ]
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features); await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
spyOn(_converse.api, "trigger").and.callThrough();
spyOn(window, 'prompt').and.callFake(() => "Please join!");
const view = _converse.chatboxviews.get('lounge@montague.lit'); const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.model.getOwnAffiliation()).toBe('owner'); expect(view.model.getOwnAffiliation()).toBe('owner');
expect(view.model.features.get('open')).toBe(false); expect(view.model.features.get('open')).toBe(false);
expect(view.el.querySelectorAll('input.invited-contact').length).toBe(1);
expect(view.el.querySelector('.occupants-header .fa-user-plus')).not.toBe(null);
// Members can't invite if the room isn't open
view.model.getOwnOccupant().set('affiliation', 'member'); view.model.getOwnOccupant().set('affiliation', 'member');
await u.waitUntil(() => view.el.querySelectorAll('input.invited-contact').length === 0); await u.waitUntil(() => view.el.querySelector('.occupants-header .fa-user-plus') === null);
view.model.features.set('open', 'true'); view.model.features.set('open', 'true');
await u.waitUntil(() => view.el.querySelector('.occupants-header .fa-user-plus'));
view.el.querySelector('.occupants-header .fa-user-plus').click();
const modal = view.sidebar_view.muc_invite_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000)
expect(modal.el.querySelectorAll('#invitee_jids').length).toBe(1);
expect(modal.el.querySelectorAll('textarea').length).toBe(1);
spyOn(view.model, 'directInvite').and.callThrough(); spyOn(view.model, 'directInvite').and.callThrough();
await u.waitUntil(() => view.el.querySelectorAll('input.invited-contact').length);
const input = view.el.querySelector('input.invited-contact'); const input = modal.el.querySelector('#invitee_jids');
expect(input.getAttribute('placeholder')).toBe('Invite');
input.value = "Balt"; input.value = "Balt";
modal.el.querySelector('button[type="submit"]').click();
await u.waitUntil(() => modal.el.querySelector('.error'));
const error = modal.el.querySelector('.error');
expect(error.textContent).toBe('Please enter a valid XMPP address');
let evt = new Event('input'); let evt = new Event('input');
input.dispatchEvent(evt); input.dispatchEvent(evt);
let sent_stanza; let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza)); spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
const hint = await u.waitUntil(() => view.el.querySelector('.suggestion-box__results li')); const hint = await u.waitUntil(() => modal.el.querySelector('.suggestion-box__results li'));
expect(input.value).toBe('Balt'); expect(input.value).toBe('Balt');
expect(hint.textContent.trim()).toBe('Balthasar'); expect(hint.textContent.trim()).toBe('Balthasar');
evt = new Event('mousedown', {'bubbles': true}); evt = new Event('mousedown', {'bubbles': true});
evt.button = 0; evt.button = 0;
hint.dispatchEvent(evt); hint.dispatchEvent(evt);
expect(window.prompt).toHaveBeenCalled();
const textarea = modal.el.querySelector('textarea');
textarea.value = "Please join!";
modal.el.querySelector('button[type="submit"]').click();
expect(view.model.directInvite).toHaveBeenCalled(); expect(view.model.directInvite).toHaveBeenCalled();
expect(sent_stanza.toLocaleString()).toBe( expect(sent_stanza.toLocaleString()).toBe(
`<message from="romeo@montague.lit/orchard" `+ `<message from="romeo@montague.lit/orchard" `+
......
This diff is collapsed.
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
}, },
"gitHead": "9641dcdc820e029b05930479c242d2b707bbe8e2", "gitHead": "9641dcdc820e029b05930479c242d2b707bbe8e2",
"devDependencies": { "devDependencies": {
"skeletor.js": "skeletorjs/skeletor#29a6d8f707076e865133b8f36f07c76ba4b4b582", "skeletor.js": "skeletorjs/skeletor#9a4487496bd2810b2f0847acbca136333cf9cfb0",
"backbone": "1.4", "backbone": "1.4",
"backbone.browserStorage": "conversejs/backbone.browserStorage#674ba3aa0e4d0f0b0dcac48fcc7dea531012828f", "backbone.browserStorage": "conversejs/backbone.browserStorage#674ba3aa0e4d0f0b0dcac48fcc7dea531012828f",
"filesize": "^4.1.2", "filesize": "^4.1.2",
......
<div class="suggestion-box room-invite">
<form>
{[ if (o.error_message) { ]} <div class="error error-feedback">{{{o.error_message}}}</div> {[ } ]}
<div class="form-group">
<input class="form-control invited-contact suggestion-box__input"
placeholder="{{{o.label_invitation}}}"
type="text"/>
<span class="suggestion-box__additions visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span>
</div>
</form>
<ul class="suggestion-box__results suggestion-box__results--below" hidden=""></ul>
</div>
import { html } from "lit-html";
import { __ } from '@converse/headless/i18n';
import { modal_header_close_button } from "./buttons"
const i18n_invite = __('Invite');
const i18n_invite_heading = __('Invite someone to this groupchat');
const error_message = __('Please enter a valid XMPP address');
const i18n_invite_label = __('XMPP Address');
const i18n_reason = __('Optional reason for the invitation');
export default (o) => 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}>
<div class="form-group">
<label class="clearfix" for="invitee_jids">${i18n_invite_label}:</label>
${ o.invalid_invite_jid ? html`<div class="error error-feedback">${error_message}</div>` : '' }
<input class="form-control suggestion-box__input"
required="required"
name="invitee_jids"
id="invitee_jids"
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>
</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>
</div>
</form>
</div>
</div>
</div>
</div>
`;
...@@ -20,6 +20,7 @@ const i18n_archived = __('Message archiving'); ...@@ -20,6 +20,7 @@ const i18n_archived = __('Message archiving');
const i18n_archived_hint = __('Messages are archived on the server'); const i18n_archived_hint = __('Messages are archived on the server');
const i18n_features = __('Features'); const i18n_features = __('Features');
const i18n_hidden = __('Hidden'); const i18n_hidden = __('Hidden');
const i18n_invite_hint = __('Invite people to join this groupchat');
const i18n_members_only = __('Members only'); const i18n_members_only = __('Members only');
const i18n_members_only_hint = __('this groupchat is restricted to members only'); const i18n_members_only_hint = __('this groupchat is restricted to members only');
const i18n_moderated = __('Moderated'); const i18n_moderated = __('Moderated');
...@@ -32,6 +33,7 @@ const i18n_not_moderated = __('Not moderated'); ...@@ -32,6 +33,7 @@ const i18n_not_moderated = __('Not moderated');
const i18n_not_searchable_hint = __('This groupchat is not publicly searchable'); const i18n_not_searchable_hint = __('This groupchat is not publicly searchable');
const i18n_open = __('Open'); const i18n_open = __('Open');
const i18n_open_hint = __('Anyone can join this groupchat'); const i18n_open_hint = __('Anyone can join this groupchat');
const i18n_participants = __('Participants');
const i18n_password = __('Password protected') const i18n_password = __('Password protected')
const i18n_password_hint = __('This groupchat requires a password before entry'); const i18n_password_hint = __('This groupchat requires a password before entry');
const i18n_persistent = __('Persistent'); const i18n_persistent = __('Persistent');
...@@ -77,11 +79,27 @@ const tpl_features = (o) => html` ...@@ -77,11 +79,27 @@ const tpl_features = (o) => html`
</div> </div>
`; `;
const invite_button = (o) => {
if (o.invitesAllowed()) {
return html`
<a class="fa fa-user-plus"
title="${i18n_invite_hint}"
@click=${o.showInviteModal}
data-toggle="modal"
data-target="#muc-invite-modal"></a>`;
} else {
return '';
}
}
export default (o) => html` export default (o) => html`
<div class="occupants-header"> <div class="occupants-header">
<i class="hide-occupants fa fa-times"></i> <i class="hide-occupants fa fa-times"></i>
<p class="occupants-heading">${o.label_occupants}</p> <div class="occupants-header--title">
<span class="occupants-heading">${i18n_participants}</span>
${ invite_button(o) }
</div>
</div> </div>
<div class="dragresize dragresize-occupants-left"></div> <div class="dragresize dragresize-occupants-left"></div>
<ul class="occupant-list"> <ul class="occupant-list">
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment