Show MUC buttons in a dropdown menu
- Get rid of the ChatBoxHeading class - Add support for showing standalone buttons in overlay viewmode
This commit is contained in:
parent
2a7773dce5
commit
3400acbfeb
5983
package-lock.json
generated
5983
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -61,6 +61,10 @@
|
||||
"@babel/preset-env": "^7.5.4",
|
||||
"@converse/headless": "file:src/headless",
|
||||
"@fortawesome/fontawesome-free": "5.9.0",
|
||||
"@lit-element-bootstrap/button": "^1.0.0",
|
||||
"@lit-element-bootstrap/button-group": "^1.0.0",
|
||||
"@lit-element-bootstrap/dropdown": "^1.0.0-alpha.4",
|
||||
"@open-wc/building-webpack": "^2.12.0",
|
||||
"autoprefixer": "^9.6.1",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
@ -75,6 +79,7 @@
|
||||
"eslint": "^6.3.0",
|
||||
"eslint-plugin-lodash": "^5.1.0",
|
||||
"exports-loader": "^0.7.0",
|
||||
"fa-icons": "^0.1.9",
|
||||
"fast-text-encoding": "^1.0.0",
|
||||
"file-loader": "^4.0.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
@ -109,6 +114,5 @@
|
||||
"webpack-dev-server": "^3.8.0",
|
||||
"webpack-merge": "^4.2.1",
|
||||
"xss": "^1.0.6"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@
|
||||
color: #ffffff;
|
||||
font-size: 100%;
|
||||
margin: 0;
|
||||
padding: 1rem;;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
&.chat-head-chatbox {
|
||||
@ -54,22 +54,27 @@
|
||||
|
||||
.chat-head__desc {
|
||||
color: var(--chat-head-color-lighten-50-percent);
|
||||
font-size: 75%;
|
||||
font-size: 80%;
|
||||
font-size: var(--font-size-small);
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
padding: 0.5rem 1rem 1rem 1rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbox-title {
|
||||
padding: 0.75rem 1rem 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbox-title--no-desc {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.chatbox-title--row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -137,7 +142,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background-color: var(--chat-head-color);
|
||||
box-shadow: 1px 3px 5px 3px rgba(0, 0, 0, 0.4);
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
@ -175,7 +179,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
background-color: var(--chat-head-color);
|
||||
border-bottom-left-radius: var(--chatbox-border-radius);
|
||||
border-bottom-right-radius: var(--chatbox-border-radius);
|
||||
@ -426,8 +429,10 @@
|
||||
|
||||
#conversejs.converse-embedded,
|
||||
#conversejs.converse-overlayed {
|
||||
.chat-head {
|
||||
.controlbox-head {
|
||||
padding: 0.5em;
|
||||
}
|
||||
.chat-head {
|
||||
border-top-left-radius: var(--chatbox-border-radius);
|
||||
border-top-right-radius: var(--chatbox-border-radius);
|
||||
@media screen and (max-height: $mobile-landscape-height) {
|
||||
@ -474,6 +479,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.chat-body {
|
||||
height: calc(100% - var(--overlayed-chat-head-height));
|
||||
}
|
||||
|
||||
.chatbox-title {
|
||||
padding: 0.5rem 0.75rem 0 0.75rem;
|
||||
}
|
||||
.chatbox-title--no-desc {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
converse-dropdown {
|
||||
.btn--standalone {
|
||||
padding: 0 0 0 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -497,12 +518,6 @@
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.chat-head {
|
||||
.chat-head__desc {
|
||||
font-size: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.chatbox {
|
||||
margin: 0;
|
||||
.box-flyout {
|
||||
@ -590,7 +605,6 @@
|
||||
}
|
||||
.chatbox {
|
||||
.box-flyout {
|
||||
background-color: var(--chat-head-color);
|
||||
box-shadow: none;
|
||||
height: var(--fullpage-chat-height);
|
||||
min-height: calc(var(--fullpage-chat-height) / 2);
|
||||
@ -598,6 +612,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
.chat-body {
|
||||
height: calc(100% - var(--fullpage-chat-head-height));
|
||||
background-color: var(--chat-head-color);
|
||||
}
|
||||
.chat-title {
|
||||
|
@ -42,15 +42,28 @@
|
||||
border-bottom: var(--chatroom-head-border-bottom);
|
||||
|
||||
.chat-head__desc {
|
||||
color: var(--chatroom-head-description-color);
|
||||
color: var(--chatroom-head-color);
|
||||
display: var(--chatroom-head-description-display);
|
||||
font-size: 70%;
|
||||
margin-top: 3px;
|
||||
border-left: var(--chatroom-head-description-border-left);
|
||||
padding-left: var(--chatroom-head-description-padding-left);
|
||||
a {
|
||||
color: var(--chatroom-head-description-link-color);
|
||||
}
|
||||
&:hover {
|
||||
button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chatbox-title {
|
||||
.btn--transparent {
|
||||
i {
|
||||
color: var(--chatroom-head-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chatbox-title__buttons {
|
||||
background-color: var(--chatroom-head-bg-color);
|
||||
}
|
||||
|
||||
a, a:visited, a:hover, a:not([href]):not([tabindex]) {
|
||||
@ -73,6 +86,7 @@
|
||||
display: var(--heading-display);
|
||||
font-weight: var(--chatroom-head-title-font-weight);
|
||||
padding-right: var(--chatroom-head-title-padding-right);
|
||||
margin: auto 0;
|
||||
.chatroom-jid {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
@ -214,7 +228,6 @@
|
||||
overflow-y: auto;
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
border-bottom: var(--occupants-border-bottom);
|
||||
}
|
||||
li {
|
||||
cursor: default;
|
||||
@ -467,10 +480,6 @@
|
||||
.box-flyout {
|
||||
width: 100%;
|
||||
|
||||
.chat-head__desc {
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
.chatroom-body {
|
||||
.chat-area {
|
||||
&.full {
|
||||
|
@ -193,6 +193,20 @@ body.converse-fullscreen {
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.5rem 1rem;
|
||||
.fa {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
&:active, &.selected {
|
||||
color: white !important;
|
||||
background-color: var(--list-item-open-color);
|
||||
.fa {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
position: fixed;
|
||||
}
|
||||
@ -352,6 +366,10 @@ body.converse-fullscreen {
|
||||
.fa, .far, .fas {
|
||||
color: #fff;
|
||||
margin-right: 0.5em;
|
||||
|
||||
&.only-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,9 +519,9 @@ body.converse-fullscreen {
|
||||
}
|
||||
|
||||
.avatar-autocomplete {
|
||||
margin-right: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.activated {
|
||||
display: block !important;
|
||||
@ -541,6 +559,11 @@ body.converse-fullscreen {
|
||||
}
|
||||
}
|
||||
|
||||
.btn--transparent {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-circle {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
|
@ -117,11 +117,8 @@ $mobile_portrait_length: 480px !default;
|
||||
--chatroom-head-button-color: var(--chatroom-head-bg-color);
|
||||
--chatroom-head-title-font-weight: normal;
|
||||
--chatroom-head-title-padding-right: 0px;
|
||||
--chatroom-head-description-color: var(--chatroom-head-bg-color-lighten-25-percent);
|
||||
--chatroom-head-description-link-color: white;
|
||||
--chatroom-head-description-display: block;
|
||||
--chatroom-head-description-border-left: 0px;
|
||||
--chatroom-head-description-padding-left: 0px;
|
||||
--chatroom-head-border-bottom: 0px;
|
||||
--chatroom-width: 500px;
|
||||
--chatroom-correcting-color: #fadfd7; // lighten($red, 30%)
|
||||
|
@ -36,7 +36,7 @@
|
||||
const view = _converse.chatboxviews.get(jid);
|
||||
spyOn(view, 'renderBookmarkForm').and.callThrough();
|
||||
spyOn(view, 'closeForm').and.callThrough();
|
||||
await u.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
|
||||
await u.waitUntil(() => view.el.querySelector('.toggle-bookmark') !== null);
|
||||
let toggle = view.el.querySelector('.toggle-bookmark');
|
||||
expect(toggle.title).toBe('Bookmark this groupchat');
|
||||
toggle.click();
|
||||
@ -216,8 +216,7 @@
|
||||
);
|
||||
await _converse.api.rooms.open(`lounge@montague.lit`);
|
||||
const view = _converse.chatboxviews.get('lounge@montague.lit');
|
||||
let bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
|
||||
expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
|
||||
expect(view.el.querySelector('.chatbox-title__text .fa-bookmark')).toBe(null);
|
||||
_converse.bookmarks.create({
|
||||
'jid': view.model.get('jid'),
|
||||
'autojoin': false,
|
||||
@ -225,11 +224,9 @@
|
||||
'nick': ' some1'
|
||||
});
|
||||
view.model.set('bookmarked', true);
|
||||
bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
|
||||
expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy();
|
||||
expect(view.el.querySelector('.chatbox-title__text .fa-bookmark')).not.toBe(null);
|
||||
view.model.set('bookmarked', false);
|
||||
bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
|
||||
expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
|
||||
expect(view.el.querySelector('.chatbox-title__text .fa-bookmark')).toBe(null);
|
||||
done();
|
||||
}));
|
||||
|
||||
@ -256,14 +253,12 @@
|
||||
expect(_converse.bookmarks.length).toBe(1);
|
||||
await u.waitUntil(() => _converse.chatboxes.length >= 1);
|
||||
expect(view.model.get('bookmarked')).toBeTruthy();
|
||||
let bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
|
||||
expect(u.hasClass('button-on', bookmark_icon)).toBeTruthy();
|
||||
|
||||
expect(view.el.querySelector('.chatbox-title__text .fa-bookmark')).not.toBe(null);
|
||||
spyOn(_converse.connection, 'getUniqueId').and.callThrough();
|
||||
const bookmark_icon = view.el.querySelector('.toggle-bookmark');
|
||||
bookmark_icon.click();
|
||||
bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
|
||||
expect(view.toggleBookmark).toHaveBeenCalled();
|
||||
expect(u.hasClass('button-on', bookmark_icon)).toBeFalsy();
|
||||
expect(view.el.querySelector('.chatbox-title__text .fa-bookmark')).toBe(null);
|
||||
expect(_converse.bookmarks.length).toBe(0);
|
||||
|
||||
// Check that an IQ stanza is sent out, containing no
|
||||
|
@ -364,9 +364,6 @@
|
||||
|
||||
expect(trimmedview.restore).toHaveBeenCalled();
|
||||
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
|
||||
const toggle_el = sizzle('.toggle-chatbox-button', chatview.el).pop();
|
||||
expect(u.hasClass('fa-minus', toggle_el)).toBeTruthy();
|
||||
expect(u.hasClass('fa-plus', toggle_el)).toBeFalsy();
|
||||
expect(chatview.model.get('minimized')).toBeFalsy();
|
||||
done();
|
||||
}));
|
||||
|
@ -149,6 +149,7 @@
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length);
|
||||
const hlview = _converse.chatboxviews.get('notify.example.com');
|
||||
await u.isVisible(hlview.el);
|
||||
const close_el = hlview.el.querySelector('.close-chatbox-button');
|
||||
close_el.click();
|
||||
await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length === 0);
|
||||
|
21
spec/muc.js
21
spec/muc.js
@ -1390,9 +1390,7 @@
|
||||
}).up()
|
||||
.c('status', {code: '110'});
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(u.isVisible(view.el.querySelector('.toggle-chatbox-button'))).toBeTruthy();
|
||||
await u.waitUntil(() => !_.isNull(view.el.querySelector('.configure-chatroom-button')))
|
||||
expect(u.isVisible(view.el.querySelector('.configure-chatroom-button'))).toBeTruthy();
|
||||
await u.waitUntil(() => view.el.querySelector('.configure-chatroom-button') !== null);
|
||||
view.el.querySelector('.configure-chatroom-button').click();
|
||||
|
||||
/* Check that an IQ is sent out, asking for the
|
||||
@ -1949,13 +1947,14 @@
|
||||
|
||||
// Members can't invite if the room isn't open
|
||||
view.model.getOwnOccupant().set('affiliation', 'member');
|
||||
|
||||
await u.waitUntil(() => view.el.querySelector('.open-invite-modal') === null);
|
||||
|
||||
view.model.features.set('open', 'true');
|
||||
await u.waitUntil(() => view.el.querySelector('.open-invite-modal'));
|
||||
|
||||
view.el.querySelector('.open-invite-modal').click();
|
||||
const modal = view.sidebar_view.muc_invite_modal;
|
||||
const modal = view.muc_invite_modal;
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000)
|
||||
|
||||
expect(modal.el.querySelectorAll('#invitee_jids').length).toBe(1);
|
||||
@ -2174,8 +2173,8 @@
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
|
||||
await new Promise(resolve => view.model.once('change:subject', resolve));
|
||||
expect(sizzle('.chat-event:last').pop().textContent.trim()).toBe('Topic set by ralphm');
|
||||
expect(sizzle('.chat-topic:last').pop().textContent.trim()).toBe(text);
|
||||
|
||||
expect(sizzle('.chat-event:last', view.el).pop().textContent.trim()).toBe('Topic set by ralphm');
|
||||
expect(view.el.querySelector('.chat-head__desc').textContent.trim()).toBe(text);
|
||||
|
||||
stanza = u.toStanza(
|
||||
@ -2185,7 +2184,6 @@
|
||||
</message>`);
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
await new Promise(resolve => view.once('messageInserted', resolve));
|
||||
expect(sizzle('.chat-topic', view.el).length).toBe(1);
|
||||
expect(sizzle('.chat-msg__subject', view.el).length).toBe(1);
|
||||
expect(sizzle('.chat-msg__subject', view.el).pop().textContent.trim()).toBe('This is a message subject');
|
||||
expect(sizzle('.chat-msg__text').length).toBe(1);
|
||||
@ -2199,7 +2197,7 @@
|
||||
</message>`);
|
||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||
await new Promise(resolve => view.model.once('change:subject', resolve));
|
||||
expect(view.el.querySelector('.chat-head__desc').textContent.trim()).toBe("");
|
||||
expect(view.el.querySelector('.chat-head__desc')).toBe(null);
|
||||
expect(view.el.querySelector('.chat-info:last-child').textContent.trim()).toBe("Topic cleared by ralphm");
|
||||
done();
|
||||
}));
|
||||
@ -2218,7 +2216,7 @@
|
||||
'author': 'ralphm'
|
||||
}});
|
||||
expect(sizzle('.chat-event:last').pop().textContent.trim()).toBe('Topic set by ralphm');
|
||||
expect(sizzle('.chat-topic:last').pop().textContent.trim()).toBe(subject);
|
||||
expect(view.el.querySelector('.chat-head__desc').textContent.trim()).toBe(subject);
|
||||
done();
|
||||
}));
|
||||
|
||||
@ -2875,8 +2873,9 @@
|
||||
spyOn(_converse.api, "trigger").and.callThrough();
|
||||
spyOn(view.model, 'leave');
|
||||
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
|
||||
spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
|
||||
view.el.querySelector('.close-chatbox-button').click();
|
||||
expect(view.close).toHaveBeenCalled();
|
||||
await u.waitUntil(() => view.close.calls.count());
|
||||
expect(view.model.leave).toHaveBeenCalled();
|
||||
await u.waitUntil(() => _converse.api.trigger.calls.count());
|
||||
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
|
||||
@ -4853,7 +4852,7 @@
|
||||
await u.waitUntil(() => _converse.chatboxes.length > 1);
|
||||
expect(sizzle('.chatroom', _converse.el).filter(u.isVisible).length).toBe(1); // There should now be an open chatroom
|
||||
var view = _converse.chatboxviews.get('inverness@chat.shakespeare.lit');
|
||||
expect(view.el.querySelector('.chat-head-chatroom').textContent.trim()).toBe("Macbeth's Castle");
|
||||
expect(view.el.querySelector('.chatbox-title__text').textContent.trim()).toBe("Macbeth's Castle");
|
||||
done();
|
||||
}));
|
||||
|
||||
|
@ -95,6 +95,7 @@
|
||||
`</presence>`)
|
||||
|
||||
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true");
|
||||
await u.waitUntil(() => !u.isVisible(modal.el));
|
||||
cbview.el.querySelector('.change-status').click()
|
||||
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000);
|
||||
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
|
||||
|
@ -23,7 +23,6 @@
|
||||
await u.waitUntil(() => _converse.chatboxes.length > 1);
|
||||
const view = _converse.chatboxviews.get(contact_jid);
|
||||
let show_modal_button = view.el.querySelector('.show-user-details-modal');
|
||||
expect(u.isVisible(show_modal_button)).toBeTruthy();
|
||||
show_modal_button.click();
|
||||
const modal = view.user_details_modal;
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
@ -33,7 +32,7 @@
|
||||
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));
|
||||
show_modal_button = view.el.querySelector('.show-user-details-modal');
|
||||
show_modal_button.click();
|
||||
remove_contact_button = modal.el.querySelector('button.remove-contact');
|
||||
@ -51,7 +50,6 @@
|
||||
await test_utils.openChatBoxFor(_converse, contact_jid)
|
||||
const view = _converse.chatboxviews.get(contact_jid);
|
||||
let show_modal_button = view.el.querySelector('.show-user-details-modal');
|
||||
expect(u.isVisible(show_modal_button)).toBeTruthy();
|
||||
show_modal_button.click();
|
||||
const modal = view.user_details_modal;
|
||||
await u.waitUntil(() => u.isVisible(modal.el), 2000);
|
||||
|
91
src/components/dropdown.js
Normal file
91
src/components/dropdown.js
Normal file
@ -0,0 +1,91 @@
|
||||
import { html } from 'lit-element';
|
||||
import { CustomElement } from './element.js';
|
||||
import { until } from 'lit-html/directives/until.js';
|
||||
import DOMNavigator from "../dom-navigator";
|
||||
import converse from "@converse/headless/converse-core";
|
||||
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
|
||||
export class Dropdown extends CustomElement {
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
'items': { type: Array }
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="dropleft">
|
||||
<button type="button" class="btn btn--transparent btn--standalone" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-bars only-icon"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
${ this.items.map(b => until(b, '')) }
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated () {
|
||||
this.menu = this.querySelector('.dropdown-menu');
|
||||
this.dropdown = this.firstElementChild;
|
||||
this.button = this.dropdown.querySelector('button');
|
||||
this.dropdown.addEventListener('click', ev => this.toggleMenu(ev));
|
||||
this.dropdown.addEventListener('keyup', ev => this.handleKeyUp(ev));
|
||||
document.addEventListener('click', ev => !this.contains(ev.target) && this.hideMenu(ev));
|
||||
this.initArrowNavigation();
|
||||
}
|
||||
|
||||
initArrowNavigation () {
|
||||
if (!this.navigator) {
|
||||
const options = {
|
||||
'selector': '.dropdown-item',
|
||||
'onSelected': el => el.focus()
|
||||
};
|
||||
this.navigator = new DOMNavigator(this.menu, options);
|
||||
}
|
||||
}
|
||||
|
||||
enableArrowNavigation (ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
this.navigator.enable();
|
||||
this.navigator.select(this.menu.firstElementChild);
|
||||
}
|
||||
|
||||
hideMenu () {
|
||||
u.removeClass('show', this.menu);
|
||||
this.navigator.disable();
|
||||
this.button.setAttribute('aria-expanded', false);
|
||||
this.button.blur();
|
||||
}
|
||||
|
||||
showMenu () {
|
||||
u.addClass('show', this.menu);
|
||||
this.button.setAttribute('aria-expanded', true);
|
||||
}
|
||||
|
||||
toggleMenu () {
|
||||
if (u.hasClass('show', this.menu)) {
|
||||
this.hideMenu();
|
||||
} else {
|
||||
this.showMenu();
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyUp (ev) {
|
||||
if (ev.keyCode === converse.keycodes.ESCAPE) {
|
||||
this.hideMenu();
|
||||
} else if (ev.keyCode === converse.keycodes.DOWN_ARROW && !this.navigator.enabled) {
|
||||
this.enableArrowNavigation(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('converse-dropdown', Dropdown);
|
9
src/components/element.js
Normal file
9
src/components/element.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { LitElement } from 'lit-element';
|
||||
|
||||
export class CustomElement extends LitElement {
|
||||
|
||||
createRenderRoot () {
|
||||
// Render without the shadow DOM
|
||||
return this;
|
||||
}
|
||||
}
|
@ -7,12 +7,10 @@
|
||||
import "@converse/headless/converse-muc";
|
||||
import { Model } from 'skeletor.js/src/model.js';
|
||||
import { View } from 'skeletor.js/src/view.js';
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import converse from "@converse/headless/converse-core";
|
||||
import tpl_bookmarks_list from "templates/bookmarks_list.js"
|
||||
import tpl_muc_bookmark_form from "templates/muc_bookmark_form.js";
|
||||
import tpl_chatroom_bookmark_toggle from "templates/chatroom_bookmark_toggle.html";
|
||||
|
||||
const { Strophe, _ } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
@ -37,21 +35,24 @@ converse.plugins.add('converse-bookmark-views', {
|
||||
// plugin architecture they will replace existing methods on the
|
||||
// relevant objects or classes.
|
||||
ChatRoomView: {
|
||||
events: {
|
||||
'click .toggle-bookmark': 'toggleBookmark'
|
||||
},
|
||||
getHeadingButtons () {
|
||||
const { _converse } = this.__super__;
|
||||
const buttons = this.__super__.getHeadingButtons.call(this);
|
||||
if (_converse.allow_bookmarks) {
|
||||
const supported = _converse.checkBookmarksSupport();
|
||||
const info_toggle_bookmark = this.model.get('bookmarked') ? __('Unbookmark this groupchat') : __('Bookmark this groupchat');
|
||||
const bookmarked = this.model.get('bookmarked');
|
||||
const template = html`<a class="chatbox-btn toggle-bookmark fa fa-bookmark ${bookmarked ? 'button-on' : ''}" title="${info_toggle_bookmark}"></a>`;
|
||||
const data = {
|
||||
'i18n_title': bookmarked ? __('Unbookmark this groupchat') : __('Bookmark this groupchat'),
|
||||
'i18n_text': bookmarked ? __('Unbookmark') : __('Bookmark'),
|
||||
'handler': ev => this.toggleBookmark(ev),
|
||||
'a_class': 'toggle-bookmark',
|
||||
'icon_class': 'fa-bookmark',
|
||||
'name': 'bookmark'
|
||||
}
|
||||
const names = buttons.map(t => t.name);
|
||||
const idx = names.indexOf('configure');
|
||||
const template_promise = supported.then(s => s ? template : '');
|
||||
return idx > -1 ? [...buttons.slice(0, idx), template_promise, ...buttons.slice(idx)] : [template_promise, ...buttons];
|
||||
const data_promise = supported.then(s => s ? data : '');
|
||||
return idx > -1 ? [...buttons.slice(0, idx), data_promise, ...buttons.slice(idx)] : [data_promise, ...buttons];
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
@ -100,25 +101,6 @@ converse.plugins.add('converse-bookmark-views', {
|
||||
});
|
||||
|
||||
const bookmarkableChatRoomView = {
|
||||
|
||||
renderBookmarkToggle () {
|
||||
const bookmark_button = tpl_chatroom_bookmark_toggle(
|
||||
_.assignIn(this.model.toJSON(), {
|
||||
'info_toggle_bookmark': this.model.get('bookmarked') ?
|
||||
__('Unbookmark this groupchat') :
|
||||
__('Bookmark this groupchat'),
|
||||
'bookmarked': this.model.get('bookmarked')
|
||||
}));
|
||||
|
||||
const buttons_row = this.el.querySelector('.chatbox-title__buttons')
|
||||
const close_button = buttons_row.querySelector('.close-chatbox-button');
|
||||
if (close_button) {
|
||||
close_button.insertAdjacentHTML('afterend', bookmark_button);
|
||||
} else {
|
||||
buttons_row.insertAdjacentHTML('beforeEnd', bookmark_button);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set whether the groupchat is bookmarked or not.
|
||||
* @private
|
||||
|
@ -7,7 +7,6 @@ import "converse-chatboxviews";
|
||||
import "converse-message-view";
|
||||
import "converse-modal";
|
||||
import { debounce, get, isString } from "lodash";
|
||||
import { View } from "skeletor.js/src/view";
|
||||
import { Overview } from "skeletor.js/src/overview";
|
||||
import { html, render } from "lit-html";
|
||||
import converse from "@converse/headless/converse-core";
|
||||
@ -73,78 +72,10 @@ converse.plugins.add('converse-chatview', {
|
||||
});
|
||||
|
||||
|
||||
_converse.ChatBoxHeading = View.extend({
|
||||
initialize () {
|
||||
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
|
||||
|
||||
this.debouncedRender = debounce(this.render, 50);
|
||||
this.listenTo(this.model, 'vcard:change', this.debouncedRender);
|
||||
|
||||
if (this.model.contact) {
|
||||
this.listenTo(this.model.contact, 'destroy', this.debouncedRender);
|
||||
}
|
||||
if (this.model.rosterContactAdded) {
|
||||
this.model.rosterContactAdded.then(() => {
|
||||
this.listenTo(this.model.contact, 'change:nickname', this.debouncedRender);
|
||||
this.debouncedRender();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
const vcard = get(this.model, 'vcard');
|
||||
const vcard_json = vcard ? vcard.toJSON() : {};
|
||||
render(tpl_chatbox_head(
|
||||
Object.assign(
|
||||
vcard_json,
|
||||
this.model.toJSON(),
|
||||
{ '_converse': _converse,
|
||||
'buttons': this.getHeadingButtons(),
|
||||
'display_name': this.model.getDisplayName()
|
||||
}
|
||||
)
|
||||
), this.el);
|
||||
return this;
|
||||
},
|
||||
|
||||
getHeadingButtons () {
|
||||
const buttons = [];
|
||||
if (!_converse.singleton) {
|
||||
const info_close = __('Close this chat box');
|
||||
const template = html`<a class="chatbox-btn close-chatbox-button fa fa-times" title="${info_close}"></a>`;
|
||||
template.name = 'close';
|
||||
buttons.push(template);
|
||||
}
|
||||
const info_details = __('Show more details about this groupchat');
|
||||
const template = html`<a class="chatbox-btn show-user-details-modal fa fa-id-card" title="${info_details}"></a>`;
|
||||
template.name = 'details';
|
||||
buttons.push(template);
|
||||
return buttons;
|
||||
},
|
||||
|
||||
onStatusMessageChanged (item) {
|
||||
this.debouncedRender();
|
||||
/**
|
||||
* When a contact's custom status message has changed.
|
||||
* @event _converse#contactStatusMessageChanged
|
||||
* @type {object}
|
||||
* @property { object } contact - The chat buddy
|
||||
* @property { string } message - The message text
|
||||
* @example _converse.api.listen.on('contactStatusMessageChanged', obj => { ... });
|
||||
*/
|
||||
_converse.api.trigger('contactStatusMessageChanged', {
|
||||
'contact': item.attributes,
|
||||
'message': item.get('status')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
_converse.UserDetailsModal = _converse.BootstrapModal.extend({
|
||||
id: "user-details-modal",
|
||||
|
||||
events: {
|
||||
'click button.remove-contact': 'removeContact',
|
||||
'click button.refresh-contact': 'refreshContact',
|
||||
'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
|
||||
},
|
||||
@ -169,11 +100,12 @@ converse.plugins.add('converse-chatview', {
|
||||
return tpl_user_details_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
vcard_json, {
|
||||
'view': this,
|
||||
'_converse': _converse,
|
||||
'allow_contact_removal': _converse.allow_contact_removal,
|
||||
'display_name': this.model.getDisplayName(),
|
||||
'is_roster_contact': this.model.contact !== undefined,
|
||||
'removeContact': ev => this.removeContact(ev),
|
||||
'view': this,
|
||||
'utils': u
|
||||
}));
|
||||
},
|
||||
@ -208,16 +140,22 @@ converse.plugins.add('converse-chatview', {
|
||||
const result = confirm(__("Are you sure you want to remove this contact?"));
|
||||
if (result === true) {
|
||||
this.modal.hide();
|
||||
this.model.contact.removeFromRoster(
|
||||
() => this.model.contact.destroy(),
|
||||
(err) => {
|
||||
log.error(err);
|
||||
_converse.api.alert('error', __('Error'), [
|
||||
__('Sorry, there was an error while trying to remove %1$s as a contact.',
|
||||
this.model.contact.getDisplayName())
|
||||
]);
|
||||
}
|
||||
);
|
||||
// XXX: This is annoying but necessary to get tests to pass.
|
||||
// The `dismissHandler` in bootstrap.native tries to
|
||||
// reference the remove button after it's been cleared from
|
||||
// the DOM, so we delay removing the contact to give it time.
|
||||
setTimeout(() => {
|
||||
this.model.contact.removeFromRoster(
|
||||
() => this.model.contact.destroy(),
|
||||
(err) => {
|
||||
log.error(err);
|
||||
_converse.api.alert('error', __('Error'), [
|
||||
__('Sorry, there was an error while trying to remove %1$s as a contact.',
|
||||
this.model.contact.getDisplayName())
|
||||
]);
|
||||
}
|
||||
);
|
||||
}, 1);
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -239,10 +177,8 @@ converse.plugins.add('converse-chatview', {
|
||||
'click .chat-msg__action-edit': 'onMessageEditButtonClicked',
|
||||
'click .chat-msg__action-retract': 'onMessageRetractButtonClicked',
|
||||
'click .chatbox-navback': 'showControlBox',
|
||||
'click .close-chatbox-button': 'close',
|
||||
'click .new-msgs-indicator': 'viewUnreadMessages',
|
||||
'click .send-button': 'onFormSubmitted',
|
||||
'click .show-user-details-modal': 'showUserDetailsModal',
|
||||
'click .spoiler-toggle': 'toggleSpoilerMessage',
|
||||
'click .toggle-call': 'toggleCall',
|
||||
'click .toggle-clear': 'clearMessages',
|
||||
@ -258,6 +194,7 @@ converse.plugins.add('converse-chatview', {
|
||||
|
||||
async initialize () {
|
||||
this.initDebounced();
|
||||
|
||||
this.listenTo(this.model.messages, 'add', this.onMessageAdded);
|
||||
this.listenTo(this.model.messages, 'rendered', this.scrollDown);
|
||||
this.model.messages.on('reset', () => {
|
||||
@ -265,13 +202,24 @@ converse.plugins.add('converse-chatview', {
|
||||
this.removeAll();
|
||||
});
|
||||
|
||||
this.listenTo(this.model, 'show', this.show);
|
||||
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
|
||||
this.listenTo(this.model, 'destroy', this.remove);
|
||||
this.listenTo(this.model, 'show', this.show);
|
||||
this.listenTo(this.model, 'vcard:change', this.renderHeading);
|
||||
|
||||
if (this.model.contact) {
|
||||
this.listenTo(this.model.contact, 'destroy', this.renderHeading);
|
||||
}
|
||||
if (this.model.rosterContactAdded) {
|
||||
this.model.rosterContactAdded.then(() => {
|
||||
this.listenTo(this.model.contact, 'change:nickname', this.renderHeading);
|
||||
this.renderHeading();
|
||||
});
|
||||
}
|
||||
|
||||
this.listenTo(this.model.presence, 'change:show', this.onPresenceChanged);
|
||||
this.render();
|
||||
await this.updateAfterMessagesFetched();
|
||||
|
||||
/**
|
||||
* Triggered once the {@link _converse.ChatBoxView} has been initialized
|
||||
* @event _converse#chatBoxViewInitialized
|
||||
@ -295,7 +243,7 @@ converse.plugins.add('converse-chatview', {
|
||||
);
|
||||
this.content = this.el.querySelector('.chat-content');
|
||||
this.renderMessageForm();
|
||||
this.insertHeading();
|
||||
this.renderHeading();
|
||||
return this;
|
||||
},
|
||||
|
||||
@ -413,13 +361,67 @@ converse.plugins.add('converse-chatview', {
|
||||
}
|
||||
},
|
||||
|
||||
insertHeading () {
|
||||
this.heading = new _converse.ChatBoxHeading({'model': this.model});
|
||||
this.heading.render();
|
||||
this.heading.chatview = this;
|
||||
const flyout = this.el.querySelector('.flyout');
|
||||
flyout.insertBefore(this.heading.el, flyout.querySelector('.chat-body'));
|
||||
return this;
|
||||
renderHeading () {
|
||||
render(this.generateHeadingTemplate(), this.el.querySelector('.chat-head-chatbox'));
|
||||
},
|
||||
|
||||
async getHeadingStandaloneButton (promise_or_data) {
|
||||
const data = await promise_or_data;
|
||||
return html`<a href="#"
|
||||
class="chatbox-btn ${data.a_class} fa ${data.icon_class}"
|
||||
@click=${data.handler}
|
||||
title="${data.i18n_title}"></a>`;
|
||||
},
|
||||
|
||||
async getHeadingDropdownItem (promise_or_data) {
|
||||
const data = await promise_or_data;
|
||||
return html`<a href="#"
|
||||
class="dropdown-item ${data.a_class}"
|
||||
@click=${data.handler}
|
||||
title="${data.i18n_title}"><i class="fa ${data.icon_class}"></i>${data.i18n_text}</a>`;
|
||||
},
|
||||
|
||||
generateHeadingTemplate () {
|
||||
const vcard = get(this.model, 'vcard');
|
||||
const vcard_json = vcard ? vcard.toJSON() : {};
|
||||
const heading_btns = this.getHeadingButtons();
|
||||
const standalone_btns = heading_btns.filter(b => b.standalone);
|
||||
const dropdown_btns = heading_btns.filter(b => !b.standalone);
|
||||
return tpl_chatbox_head(
|
||||
Object.assign(
|
||||
vcard_json,
|
||||
this.model.toJSON(), {
|
||||
'_converse': _converse,
|
||||
'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)),
|
||||
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b)),
|
||||
'display_name': this.model.getDisplayName()
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
getHeadingButtons () {
|
||||
const buttons = [{
|
||||
'a_class': 'show-user-details-modal',
|
||||
'handler': ev => this.showUserDetailsModal(ev),
|
||||
'i18n_text': __('Details'),
|
||||
'i18n_title': __('See more information about this person'),
|
||||
'icon_class': 'fa-id-card',
|
||||
'name': 'details',
|
||||
'standalone': _converse.view_mode === 'overlayed',
|
||||
}];
|
||||
if (!_converse.singleton) {
|
||||
buttons.push({
|
||||
'a_class': 'close-chatbox-button',
|
||||
'handler': ev => this.close(ev),
|
||||
'i18n_text': __('Close'),
|
||||
'i18n_title': __('Close and end this conversation'),
|
||||
'icon_class': 'fa-times',
|
||||
'name': 'close',
|
||||
'standalone': _converse.view_mode === 'overlayed',
|
||||
});
|
||||
}
|
||||
return buttons;
|
||||
},
|
||||
|
||||
getToolbarOptions () {
|
||||
@ -598,6 +600,22 @@ converse.plugins.add('converse-chatview', {
|
||||
}
|
||||
},
|
||||
|
||||
onStatusMessageChanged (item) {
|
||||
this.renderHeading();
|
||||
/**
|
||||
* When a contact's custom status message has changed.
|
||||
* @event _converse#contactStatusMessageChanged
|
||||
* @type {object}
|
||||
* @property { object } contact - The chat buddy
|
||||
* @property { string } message - The message text
|
||||
* @example _converse.api.listen.on('contactStatusMessageChanged', obj => { ... });
|
||||
*/
|
||||
_converse.api.trigger('contactStatusMessageChanged', {
|
||||
'contact': item.attributes,
|
||||
'message': item.get('status')
|
||||
});
|
||||
},
|
||||
|
||||
showHelpMessages (msgs, type='info', spinner) {
|
||||
msgs.forEach(msg => {
|
||||
this.content.insertAdjacentHTML(
|
||||
|
@ -138,7 +138,8 @@ converse.plugins.add('converse-headlines-view', {
|
||||
this.listenTo(this.model, 'destroy', this.hide);
|
||||
this.listenTo(this.model, 'change:minimized', this.onMinimizedChanged);
|
||||
|
||||
this.render().insertHeading()
|
||||
this.render();
|
||||
this.renderHeading();
|
||||
this.updateAfterMessagesFetched();
|
||||
this.insertIntoDOM().hide();
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ import { Model } from 'skeletor.js/src/model.js';
|
||||
import { Overview } from "skeletor.js/src/overview";
|
||||
import { View } from "skeletor.js/src/view";
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import { html } from "lit-html";
|
||||
import converse from "@converse/headless/converse-core";
|
||||
import tpl_chats_panel from "templates/chats_panel.html";
|
||||
import tpl_toggle_chats from "templates/toggle_chats.html";
|
||||
@ -74,10 +73,6 @@ converse.plugins.add('converse-minimize', {
|
||||
},
|
||||
|
||||
ChatBoxView: {
|
||||
events: {
|
||||
'click .toggle-chatbox-button': 'minimize',
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.listenTo(this.model, 'change:minimized', this.onMinimizedChanged)
|
||||
return this.__super__.initialize.apply(this, arguments);
|
||||
@ -113,25 +108,27 @@ converse.plugins.add('converse-minimize', {
|
||||
if (!this.model.get('minimized')) {
|
||||
return this.__super__.setChatBoxWidth.call(this, width);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
ChatBoxHeading: {
|
||||
getHeadingButtons () {
|
||||
const { _converse } = this.__super__;
|
||||
const buttons = this.__super__.getHeadingButtons.call(this);
|
||||
const info_minimize = __('Minimize this chat box');
|
||||
const template = html`<a class="chatbox-btn toggle-chatbox-button fa fa-minus" title="${info_minimize}"></a>`;
|
||||
const data = {
|
||||
'a_class': 'toggle-chatbox-button',
|
||||
'handler': ev => this.minimize(ev),
|
||||
'i18n_text': __('Minimize'),
|
||||
'i18n_title': __('Minimize this chat'),
|
||||
'icon_class': "fa-minus",
|
||||
'name': 'minimize',
|
||||
'standalone': _converse.view_mode === 'overlayed'
|
||||
}
|
||||
const names = buttons.map(t => t.name);
|
||||
const idx = names.indexOf('close');
|
||||
return idx > -1 ? [...buttons.slice(0, idx+1), template, ...buttons.slice(idx+1)] : [template, ...buttons];
|
||||
return idx > -1 ? [...buttons.slice(0, idx), data, ...buttons.slice(idx)] : [data, ...buttons];
|
||||
}
|
||||
},
|
||||
|
||||
ChatRoomView: {
|
||||
events: {
|
||||
'click .toggle-chatbox-button': 'minimize',
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.listenTo(this.model, 'change:minimized', this.onMinimizedChanged)
|
||||
const result = this.__super__.initialize.apply(this, arguments);
|
||||
@ -142,12 +139,20 @@ converse.plugins.add('converse-minimize', {
|
||||
},
|
||||
|
||||
getHeadingButtons () {
|
||||
const { _converse } = this.__super__;
|
||||
const buttons = this.__super__.getHeadingButtons.call(this);
|
||||
const info_minimize = __('Minimize this groupchat');
|
||||
const template = html`<a class="chatbox-btn toggle-chatbox-button fa fa-minus" title="${info_minimize}"></a>`;
|
||||
const data = {
|
||||
'a_class': 'toggle-chatbox-button',
|
||||
'handler': ev => this.minimize(ev),
|
||||
'i18n_text': __('Minimize'),
|
||||
'i18n_title': __('Minimize this groupchat'),
|
||||
'icon_class': "fa-minus",
|
||||
'name': 'minimize',
|
||||
'standalone': _converse.view_mode === 'overlayed'
|
||||
}
|
||||
const names = buttons.map(t => t.name);
|
||||
const idx = names.indexOf('signout');
|
||||
return idx > -1 ? [...buttons.slice(0, idx+1), template, ...buttons.slice(idx+1)] : [template, ...buttons];
|
||||
return idx > -1 ? [...buttons.slice(0, idx), data, ...buttons.slice(idx)] : [data, ...buttons];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ import "@converse/headless/utils/muc";
|
||||
import { Model } from 'skeletor.js/src/model.js';
|
||||
import { View } from 'skeletor.js/src/view.js';
|
||||
import { get, head, isString, isUndefined } from "lodash";
|
||||
import { html, render } from "lit-html";
|
||||
import { render } from "lit-html";
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import converse from "@converse/headless/converse-core";
|
||||
import log from "@converse/headless/log";
|
||||
@ -649,14 +649,11 @@ converse.plugins.add('converse-muc-views', {
|
||||
'click .chat-msg__action-edit': 'onMessageEditButtonClicked',
|
||||
'click .chat-msg__action-retract': 'onMessageRetractButtonClicked',
|
||||
'click .chatbox-navback': 'showControlBox',
|
||||
'click .close-chatbox-button': 'close',
|
||||
'click .configure-chatroom-button': 'getAndRenderConfigurationForm',
|
||||
'click .hide-occupants': 'hideOccupants',
|
||||
'click .new-msgs-indicator': 'viewUnreadMessages',
|
||||
// Arrow functions don't work here because you can't bind a different `this` param to them.
|
||||
'click .occupant-nick': function (ev) {this.insertIntoTextArea(ev.target.textContent) },
|
||||
'click .send-button': 'onFormSubmitted',
|
||||
'click .show-room-details-modal': 'showRoomDetailsModal',
|
||||
'click .toggle-call': 'toggleCall',
|
||||
'click .toggle-occupants': 'toggleOccupants',
|
||||
'click .upload-file': 'toggleFileUpload',
|
||||
@ -689,6 +686,7 @@ converse.plugins.add('converse-muc-views', {
|
||||
this.listenTo(this.model, 'show', this.show);
|
||||
|
||||
this.listenTo(this.model.features, 'change:moderated', this.renderBottomPanel);
|
||||
this.listenTo(this.model.features, 'change:open', this.renderHeading);
|
||||
|
||||
this.listenTo(this.model.occupants, 'add', this.onOccupantAdded);
|
||||
this.listenTo(this.model.occupants, 'remove', this.onOccupantRemoved);
|
||||
@ -738,6 +736,7 @@ converse.plugins.add('converse-muc-views', {
|
||||
render(this.generateHeadingTemplate(), this.el.querySelector('.chat-head-chatroom'));
|
||||
},
|
||||
|
||||
|
||||
renderBottomPanel () {
|
||||
const container = this.el.querySelector('.bottom-panel');
|
||||
const entered = this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED;
|
||||
@ -1110,40 +1109,100 @@ converse.plugins.add('converse-muc-views', {
|
||||
},
|
||||
|
||||
getHeadingButtons () {
|
||||
const buttons = [];
|
||||
if (!_converse.singleton) {
|
||||
const info_close = __('Close and leave this groupchat');
|
||||
const template = html`<a class="chatbox-btn close-chatbox-button fa fa-sign-out-alt" title="${info_close}"></a>`;
|
||||
template.name = 'signout';
|
||||
buttons.push(template);
|
||||
const buttons = [{
|
||||
'i18n_text': __('Details'),
|
||||
'i18n_title': __('Show more information about this groupchat'),
|
||||
'handler': ev => this.showRoomDetailsModal(ev),
|
||||
'a_class': 'show-room-details-modal',
|
||||
'icon_class': 'fa-info-circle',
|
||||
'name': 'details'
|
||||
}];
|
||||
if (this.model.invitesAllowed()) {
|
||||
buttons.push({
|
||||
'i18n_text': __('Invite'),
|
||||
'i18n_title': __('Invite someone to join this groupchat'),
|
||||
'handler': ev => this.showInviteModal(ev),
|
||||
'a_class': 'open-invite-modal',
|
||||
'icon_class': 'fa-user-plus',
|
||||
'name': 'invite'
|
||||
});
|
||||
}
|
||||
if (this.model.getOwnAffiliation() === 'owner') {
|
||||
const info_configure = __('Configure this groupchat');
|
||||
const template = html`<a class="chatbox-btn configure-chatroom-button fa fa-wrench" title="${info_configure} "></a>`
|
||||
template.name = 'configure';
|
||||
buttons.push(template);
|
||||
buttons.push({
|
||||
'i18n_text': __('Configure'),
|
||||
'i18n_title': __('Configure this groupchat'),
|
||||
'handler': ev => this.getAndRenderConfigurationForm(ev),
|
||||
'a_class': 'configure-chatroom-button',
|
||||
'icon_class': 'fa-wrench',
|
||||
'name': 'configure'
|
||||
});
|
||||
}
|
||||
|
||||
if (this.model.get('subject')) {
|
||||
buttons.push({
|
||||
'i18n_text': this.model.get('hide_subject') ? __('Show topic') : __('Hide topic'),
|
||||
'i18n_title': this.model.get('hide_subject') ?
|
||||
__('Show the topic message in the heading') :
|
||||
__('Hide the topic in the heading'),
|
||||
'handler': ev => this.toggleTopic(ev),
|
||||
'a_class': '',
|
||||
'icon_class': 'fa-minus-square',
|
||||
'name': 'toggle-topic'
|
||||
});
|
||||
}
|
||||
|
||||
if (!_converse.singleton) {
|
||||
buttons.push({
|
||||
'i18n_text': __('Leave'),
|
||||
'i18n_title': __('Leave and close this groupchat'),
|
||||
'handler': async ev => {
|
||||
const messages = [__('Are you sure you want to leave this groupchat?')];
|
||||
const result = await _converse.api.confirm(__('Confirm'), messages);
|
||||
result && this.close(ev);
|
||||
},
|
||||
'a_class': 'close-chatbox-button',
|
||||
'standalone': _converse.view_mode === 'overlayed',
|
||||
'icon_class': 'fa-sign-out-alt',
|
||||
'name': 'signout'
|
||||
});
|
||||
}
|
||||
const info_details = __('Show more details about this groupchat');
|
||||
const template = html`<a class="chatbox-btn show-room-details-modal fa fa-info-circle" title="${info_details}"></a>`;
|
||||
template.name = 'details';
|
||||
buttons.push(template);
|
||||
return buttons;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the groupchat heading HTML to be rendered.
|
||||
* Returns the groupchat heading TemplateResult to be rendered.
|
||||
* @private
|
||||
* @method _converse.ChatRoomView#generateHeadingTemplate
|
||||
*/
|
||||
generateHeadingTemplate () {
|
||||
const heading_btns = this.getHeadingButtons();
|
||||
const standalone_btns = heading_btns.filter(b => b.standalone);
|
||||
const dropdown_btns = heading_btns.filter(b => !b.standalone);
|
||||
return tpl_chatroom_head(
|
||||
Object.assign(this.model.toJSON(), {
|
||||
_converse,
|
||||
'buttons': this.getHeadingButtons(),
|
||||
'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)),
|
||||
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b)),
|
||||
'title': this.model.getDisplayName(),
|
||||
}));
|
||||
},
|
||||
|
||||
toggleTopic () {
|
||||
this.model.save('hide_subject', !this.model.get('hide_subject'));
|
||||
},
|
||||
|
||||
|
||||
showInviteModal (ev) {
|
||||
ev.preventDefault();
|
||||
if (this.muc_invite_modal === undefined) {
|
||||
this.muc_invite_modal = new _converse.MUCInviteModal({'model': new Model()});
|
||||
// TODO: remove once we have API for sending direct invite
|
||||
this.muc_invite_modal.chatroomview = this;
|
||||
}
|
||||
this.muc_invite_modal.show(ev);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Callback method that gets called after the chat has become visible.
|
||||
* @private
|
||||
@ -1217,8 +1276,7 @@ converse.plugins.add('converse-muc-views', {
|
||||
},
|
||||
|
||||
/**
|
||||
* Show or hide the right sidebar containing the chat
|
||||
* occupants (and the invite widget).
|
||||
* Hide the right sidebar containing the chat occupants.
|
||||
* @private
|
||||
* @method _converse.ChatRoomView#hideOccupants
|
||||
*/
|
||||
@ -1232,8 +1290,7 @@ converse.plugins.add('converse-muc-views', {
|
||||
},
|
||||
|
||||
/**
|
||||
* Show or hide the right sidebar containing the chat
|
||||
* occupants (and the invite widget).
|
||||
* Show or hide the right sidebar containing the chat occupants.
|
||||
* @private
|
||||
* @method _converse.ChatRoomView#toggleOccupants
|
||||
*/
|
||||
@ -1986,26 +2043,13 @@ converse.plugins.add('converse-muc-views', {
|
||||
// replaced by the user's name.
|
||||
// Example: Topic set by JC Brand
|
||||
const message = subject.text ? __('Topic set by %1$s', author) : __('Topic cleared by %1$s', author);
|
||||
const date = (new Date()).toISOString();
|
||||
|
||||
this.content.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
tpl_info({
|
||||
'isodate': date,
|
||||
'isodate': (new Date()).toISOString(),
|
||||
'extra_classes': 'chat-event',
|
||||
'message': message
|
||||
}));
|
||||
|
||||
if (subject.text) {
|
||||
this.content.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
tpl_info({
|
||||
'isodate': date,
|
||||
'extra_classes': 'chat-topic',
|
||||
'message': u.addHyperlinks(xss.filterXSS(get(this.model.get('subject'), 'text'), {'whiteList': {}})),
|
||||
'render_message': true
|
||||
}));
|
||||
}
|
||||
this.scrollDown();
|
||||
}
|
||||
});
|
||||
@ -2198,9 +2242,7 @@ converse.plugins.add('converse-muc-views', {
|
||||
Object.assign(this.chatroomview.model.toJSON(), {
|
||||
_converse,
|
||||
'features': this.chatroomview.model.features,
|
||||
'occupants': this.model.models,
|
||||
'invitesAllowed': () => this.invitesAllowed(),
|
||||
'showInviteModal': ev => this.showInviteModal(ev)
|
||||
'occupants': this.model.models
|
||||
})
|
||||
);
|
||||
},
|
||||
@ -2218,16 +2260,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
}
|
||||
},
|
||||
|
||||
showInviteModal (ev) {
|
||||
ev.preventDefault();
|
||||
if (this.muc_invite_modal === undefined) {
|
||||
this.muc_invite_modal = new _converse.MUCInviteModal({'model': new Model()});
|
||||
// TODO: remove once we have API for sending direct invite
|
||||
this.muc_invite_modal.chatroomview = this.chatroomview;
|
||||
}
|
||||
this.muc_invite_modal.show(ev);
|
||||
},
|
||||
|
||||
setOccupantsHeight () {
|
||||
// TODO: remove the features section in sidebar and then this as well
|
||||
const el = this.el.querySelector('.chatroom-features');
|
||||
@ -2235,13 +2267,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
this.el.querySelector('.occupant-list').style.cssText =
|
||||
`height: calc(100% - ${el.offsetHeight}px - 5em);`;
|
||||
}
|
||||
},
|
||||
|
||||
invitesAllowed () {
|
||||
return _converse.allow_muc_invitations &&
|
||||
(this.chatroomview.model.features.get('open') ||
|
||||
this.chatroomview.model.getOwnAffiliation() === "owner"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -340,7 +340,6 @@ converse.plugins.add('converse-muc', {
|
||||
// mention the user and `num_unread_general` to indicate
|
||||
// generally unread messages (which *includes* mentions!).
|
||||
'num_unread_general': 0,
|
||||
|
||||
'bookmarked': false,
|
||||
'chat_state': undefined,
|
||||
'hidden': ['mobile', 'fullscreen'].includes(_converse.view_mode),
|
||||
@ -348,8 +347,8 @@ converse.plugins.add('converse-muc', {
|
||||
'name': '',
|
||||
'num_unread': 0,
|
||||
'roomconfig': {},
|
||||
'time_sent': (new Date(0)).toISOString(),
|
||||
'time_opened': this.get('time_opened') || (new Date()).getTime(),
|
||||
'time_sent': (new Date(0)).toISOString(),
|
||||
'type': _converse.CHATROOMS_TYPE
|
||||
}
|
||||
},
|
||||
@ -597,6 +596,13 @@ converse.plugins.add('converse-muc', {
|
||||
return this;
|
||||
},
|
||||
|
||||
invitesAllowed () {
|
||||
return _converse.allow_muc_invitations &&
|
||||
(this.features.get('open') ||
|
||||
this.getOwnAffiliation() === "owner"
|
||||
);
|
||||
},
|
||||
|
||||
getDisplayName () {
|
||||
const name = this.get('name');
|
||||
if (name) {
|
||||
@ -1510,7 +1516,10 @@ converse.plugins.add('converse-muc', {
|
||||
// The subject is changed by sending a message of type "groupchat" to the <room@service>,
|
||||
// where the <message/> MUST contain a <subject/> element that specifies the new subject but
|
||||
// MUST NOT contain a <body/> element (or a <thread/> element).
|
||||
u.safeSave(this, {'subject': {'author': attrs.nick, 'text': attrs.subject || ''}});
|
||||
u.safeSave(this, {
|
||||
'subject': {'author': attrs.nick, 'text': attrs.subject || ''},
|
||||
'hide_subject': attrs.subject ? false : this.get('hide_subject')
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -131,8 +131,7 @@ u.shouldCreateMessage = function (attrs) {
|
||||
|
||||
u.shouldCreateGroupchatMessage = function (attrs) {
|
||||
return attrs.nick && (u.shouldCreateMessage(attrs) || attrs.is_tombstone);
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
u.isEmptyMessage = function (attrs) {
|
||||
if (attrs instanceof Model) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
<div class="flyout box-flyout">
|
||||
<div class="chat-head chat-head-chatbox row no-gutters"></div>
|
||||
<div class="chat-body">
|
||||
<div class="chat-content {[ if (o.show_send_button) { ]}chat-content-sendbutton{[ } ]}" aria-live="polite"></div>
|
||||
<div class="bottom-panel">
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { html } from "lit-html";
|
||||
import { until } from 'lit-html/directives/until.js';
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import { until } from 'lit-html/directives/until.js';
|
||||
import avatar from "./avatar.js";
|
||||
import converse from "@converse/headless/converse-core";
|
||||
import xss from "xss/dist/xss";
|
||||
|
||||
const i18n_profile = __('The User\'s Profile Image');
|
||||
|
||||
@ -14,20 +12,24 @@ const avatar_data = {
|
||||
'width': 40,
|
||||
}
|
||||
|
||||
const tpl_standalone_btns = (o) => o.standalone_btns.reverse().map(b => until(b, ''));
|
||||
|
||||
|
||||
export default (o) => {
|
||||
return html`
|
||||
<div class="chat-head chat-head-chatbox row no-gutters">
|
||||
<div class="chatbox-title">
|
||||
<div class="chatbox-title--row">
|
||||
${ (!o._converse.singleton) ? html`<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>` : '' }
|
||||
${ (o.type !== o._converse.HEADLINES_TYPE) ? avatar(Object.assign({}, o, avatar_data)) : '' }
|
||||
<div class="chatbox-title__text" title="${o.jid}">
|
||||
${ o.url ? html`<a href="${o.url}" target="_blank" rel="noopener" class="user">${o.display_name}</a>` : o.display_name}
|
||||
</div>
|
||||
<div class="chatbox-title ${ o.status ? '' : "chatbox-title--no-desc"}">
|
||||
<div class="chatbox-title--row">
|
||||
${ (!o._converse.singleton) ? html`<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>` : '' }
|
||||
${ (o.type !== o._converse.HEADLINES_TYPE) ? avatar(Object.assign({}, o, avatar_data)) : '' }
|
||||
<div class="chatbox-title__text" title="${o.jid}">
|
||||
${ o.url ? html`<a href="${o.url}" target="_blank" rel="noopener" class="user">${o.display_name}</a>` : o.display_name}
|
||||
</div>
|
||||
<div class="chatbox-title__buttons row no-gutters">${ o.buttons.map(b => until(b, '')) }</div>
|
||||
</div>
|
||||
<p class="chat-head__desc">${ o.status }</p>
|
||||
<div class="chatbox-title__buttons row no-gutters">
|
||||
${ o.dropdown_btns.length ? html`<converse-dropdown .items=${o.dropdown_btns}></converse-dropdown>` : '' }
|
||||
${ o.standalone_btns.length ? tpl_standalone_btns(o) : '' }
|
||||
</div>
|
||||
</div>
|
||||
${ o.status ? html`<p class="chat-head__desc">${ o.status }</p>` : '' }
|
||||
`;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import '../components/dropdown.js';
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import { html } from "lit-html";
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
import { until } from 'lit-html/directives/until.js';
|
||||
@ -5,18 +7,26 @@ import converse from "@converse/headless/converse-core";
|
||||
import xss from "xss/dist/xss";
|
||||
|
||||
const u = converse.env.utils;
|
||||
const i18n_hide_topic = __('Hide the groupchat topic');
|
||||
|
||||
|
||||
const tpl_standalone_btns = (o) => o.standalone_btns.reverse().map(b => until(b, ''));
|
||||
|
||||
|
||||
export default (o) => {
|
||||
const subject = o.subject ? u.addHyperlinks(xss.filterXSS(o.subject.text, {'whiteList': {}})) : '';
|
||||
const show_subject = (subject && !o.hide_subject);
|
||||
return html`
|
||||
<div class="chatbox-title">
|
||||
${ (!o._converse.singleton) ? html`<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>` : '' }
|
||||
<div class="chatbox-title__text" title="${ (o._converse.locked_muc_domain !== 'hidden') ? o.jid : '' }">${ o.title }</div>
|
||||
<div class="chatbox-title ${ show_subject ? '' : "chatbox-title--no-desc"}">
|
||||
${ (o._converse.standalone) ? html`<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>` : '' }
|
||||
<div class="chatbox-title__text" title="${ (o._converse.locked_muc_domain !== 'hidden') ? o.jid : '' }">${ o.title }
|
||||
${ (o.bookmarked) ? html`<i class="fa fa-bookmark"></i>` : '' }
|
||||
</div>
|
||||
<div class="chatbox-title__buttons row no-gutters">
|
||||
${ o.buttons.map(b => until(b, '')) }
|
||||
${ o.standalone_btns.length ? tpl_standalone_btns(o) : '' }
|
||||
${ o.dropdown_btns.length ? html`<converse-dropdown .items=${o.dropdown_btns}></converse-dropdown>` : '' }
|
||||
</div>
|
||||
</div>
|
||||
<p class="chat-head__desc">${unsafeHTML(subject)}</p>
|
||||
${ show_subject ? html`<p class="chat-head__desc" title="${i18n_hide_topic}">${unsafeHTML(subject)}</p>` : '' }
|
||||
`;
|
||||
}
|
||||
|
@ -13,30 +13,10 @@ const PRETTY_CHAT_STATUS = {
|
||||
'online': 'Online'
|
||||
};
|
||||
|
||||
const occupant_hint = (occupant) => __('Click to mention %1$s in your message.', occupant.get('nick'))
|
||||
|
||||
const i18n_invite = (o) => o._converse.view_mode === 'overlayed' ? __('Invite') : __('Invite someone');
|
||||
const i18n_invite_hint = __('Invite someone to join this groupchat');
|
||||
const i18n_occupant_hint = (occupant) => __('Click to mention %1$s in your message.', occupant.get('nick'))
|
||||
const i18n_participants = __('Participants');
|
||||
|
||||
|
||||
const invite_widget = (o) => {
|
||||
if (o.invitesAllowed()) {
|
||||
return html`
|
||||
<a class="open-invite-modal"
|
||||
title="${i18n_invite_hint}"
|
||||
data-toggle="modal"
|
||||
data-target="#muc-invite-modal"
|
||||
@click=${o.showInviteModal}>
|
||||
<i class="btn btn-primary btn-circle fa fa-user-plus"></i>
|
||||
${i18n_invite(o)}
|
||||
</a>`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default (o) => html`
|
||||
<div class="occupants-header">
|
||||
<i class="hide-occupants fa fa-times"></i>
|
||||
@ -51,10 +31,9 @@ export default (o) => html`
|
||||
Object.assign({
|
||||
'jid': '',
|
||||
'hint_show': PRETTY_CHAT_STATUS[occupant.get('show')],
|
||||
'hint_occupant': occupant_hint(occupant)
|
||||
'hint_occupant': i18n_occupant_hint(occupant)
|
||||
}, occupant.toJSON())
|
||||
);
|
||||
}) }
|
||||
</ul>
|
||||
${ invite_widget(o) }
|
||||
`;
|
||||
|
@ -60,7 +60,13 @@ const fingerprints = (o) => {
|
||||
`;
|
||||
}
|
||||
|
||||
const remove_button = html`<button type="button" class="btn btn-danger remove-contact"><i class="far fa-trash-alt"> </i>${i18n_remove_contact}</button>`;
|
||||
const remove_button = (o) => {
|
||||
return html`
|
||||
<button type="button" @click="${o.removeContact}" class="btn btn-danger remove-contact">
|
||||
<i class="far fa-trash-alt"></i>${i18n_remove_contact}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
export default (o) => html`
|
||||
@ -84,7 +90,7 @@ export default (o) => html`
|
||||
<div class="modal-footer">
|
||||
${modal_close_button}
|
||||
<button type="button" class="btn btn-info refresh-contact"><i class="fa fa-refresh"> </i>${i18n_refresh}</button>
|
||||
${ (o.allow_contact_removal && o.is_roster_contact) ? remove_button : '' }
|
||||
${ (o.allow_contact_removal && o.is_roster_contact) ? remove_button(o) : '' }
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -275,6 +275,11 @@ u.hasClass = function (className, el) {
|
||||
return (el instanceof Element) && el.classList.contains(className);
|
||||
};
|
||||
|
||||
|
||||
u.toggleClass = function (className, el) {
|
||||
u.hasClass(className, el) ? u.removeClass(className, el) : u.addClass(className, el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a class to an element.
|
||||
* @method u#addClass
|
||||
|
Loading…
Reference in New Issue
Block a user