Componentize the chat headings
This commit is contained in:
parent
906fa93812
commit
a8a2bb4681
@ -359,7 +359,7 @@
|
|||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
&.muc-bottom-panel--muted {
|
&.muc-bottom-panel--muted {
|
||||||
height: 8em;
|
height: 4em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,7 @@ async function openModtools (_converse, view) {
|
|||||||
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
|
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
|
||||||
const bottom_panel = view.querySelector('converse-muc-bottom-panel');
|
const bottom_panel = view.querySelector('converse-muc-bottom-panel');
|
||||||
bottom_panel.onKeyDown(enter);
|
bottom_panel.onKeyDown(enter);
|
||||||
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
|
const modal = await u.waitUntil(() => _converse.api.modal.get('converse-modtools-modal'));
|
||||||
const modal = _converse.api.modal.get('converse-modtools-modal');
|
|
||||||
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
await u.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||||
return modal;
|
return modal;
|
||||||
}
|
}
|
||||||
@ -24,7 +23,6 @@ describe("The groupchat moderator tool", function () {
|
|||||||
it("allows you to set affiliations and roles",
|
it("allows you to set affiliations and roles",
|
||||||
mock.initConverse([], {}, async function (done, _converse) {
|
mock.initConverse([], {}, async function (done, _converse) {
|
||||||
|
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
|
|
||||||
const muc_jid = 'lounge@montague.lit';
|
const muc_jid = 'lounge@montague.lit';
|
||||||
|
|
||||||
let members = [
|
let members = [
|
||||||
@ -143,7 +141,6 @@ describe("The groupchat moderator tool", function () {
|
|||||||
it("allows you to filter affiliation search results",
|
it("allows you to filter affiliation search results",
|
||||||
mock.initConverse([], {}, async function (done, _converse) {
|
mock.initConverse([], {}, async function (done, _converse) {
|
||||||
|
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
|
|
||||||
const muc_jid = 'lounge@montague.lit';
|
const muc_jid = 'lounge@montague.lit';
|
||||||
const members = [
|
const members = [
|
||||||
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
|
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
|
||||||
@ -197,11 +194,9 @@ describe("The groupchat moderator tool", function () {
|
|||||||
it("allows you to filter role search results",
|
it("allows you to filter role search results",
|
||||||
mock.initConverse([], {}, async function (done, _converse) {
|
mock.initConverse([], {}, async function (done, _converse) {
|
||||||
|
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
|
|
||||||
const muc_jid = 'lounge@montague.lit';
|
const muc_jid = 'lounge@montague.lit';
|
||||||
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', []);
|
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', []);
|
||||||
const view = _converse.chatboxviews.get(muc_jid);
|
const view = _converse.chatboxviews.get(muc_jid);
|
||||||
|
|
||||||
_converse.connection._dataRecv(mock.createRequest(
|
_converse.connection._dataRecv(mock.createRequest(
|
||||||
$pres({to: _converse.jid, from: `${muc_jid}/nomorenicks`})
|
$pres({to: _converse.jid, from: `${muc_jid}/nomorenicks`})
|
||||||
.c('x', {xmlns: Strophe.NS.MUC_USER})
|
.c('x', {xmlns: Strophe.NS.MUC_USER})
|
||||||
@ -263,9 +258,8 @@ describe("The groupchat moderator tool", function () {
|
|||||||
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
|
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
|
||||||
const bottom_panel = view.querySelector('converse-muc-bottom-panel');
|
const bottom_panel = view.querySelector('converse-muc-bottom-panel');
|
||||||
bottom_panel.onKeyDown(enter);
|
bottom_panel.onKeyDown(enter);
|
||||||
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
|
|
||||||
|
|
||||||
const modal = _converse.api.modal.get('converse-modtools-modal');
|
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.el), 1000);
|
||||||
|
|
||||||
const tab = modal.el.querySelector('#roles-tab');
|
const tab = modal.el.querySelector('#roles-tab');
|
||||||
@ -307,7 +301,6 @@ describe("The groupchat moderator tool", function () {
|
|||||||
it("shows an error message if a particular affiliation list may not be retrieved",
|
it("shows an error message if a particular affiliation list may not be retrieved",
|
||||||
mock.initConverse([], {}, async function (done, _converse) {
|
mock.initConverse([], {}, async function (done, _converse) {
|
||||||
|
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
|
|
||||||
const muc_jid = 'lounge@montague.lit';
|
const muc_jid = 'lounge@montague.lit';
|
||||||
const members = [
|
const members = [
|
||||||
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
|
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
|
||||||
@ -357,7 +350,6 @@ describe("The groupchat moderator tool", function () {
|
|||||||
it("shows an error message if a particular affiliation may not be set",
|
it("shows an error message if a particular affiliation may not be set",
|
||||||
mock.initConverse([], {}, async function (done, _converse) {
|
mock.initConverse([], {}, async function (done, _converse) {
|
||||||
|
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
|
|
||||||
const muc_jid = 'lounge@montague.lit';
|
const muc_jid = 'lounge@montague.lit';
|
||||||
const members = [
|
const members = [
|
||||||
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
|
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
|
||||||
@ -422,7 +414,6 @@ describe("The groupchat moderator tool", function () {
|
|||||||
it("doesn't allow admins to make more admins",
|
it("doesn't allow admins to make more admins",
|
||||||
mock.initConverse([], {}, async function (done, _converse) {
|
mock.initConverse([], {}, async function (done, _converse) {
|
||||||
|
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
|
|
||||||
const muc_jid = 'lounge@montague.lit';
|
const muc_jid = 'lounge@montague.lit';
|
||||||
const members = [
|
const members = [
|
||||||
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
|
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
|
||||||
@ -457,7 +448,6 @@ describe("The groupchat moderator tool", function () {
|
|||||||
it("lets the assignable affiliations and roles be configured via modtools_disable_assign",
|
it("lets the assignable affiliations and roles be configured via modtools_disable_assign",
|
||||||
mock.initConverse([], {}, async function (done, _converse) {
|
mock.initConverse([], {}, async function (done, _converse) {
|
||||||
|
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
|
|
||||||
const muc_jid = 'lounge@montague.lit';
|
const muc_jid = 'lounge@montague.lit';
|
||||||
const members = [{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'}];
|
const members = [{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'}];
|
||||||
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
|
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
|
||||||
@ -467,9 +457,8 @@ describe("The groupchat moderator tool", function () {
|
|||||||
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
|
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
|
||||||
const bottom_panel = view.querySelector('converse-muc-bottom-panel');
|
const bottom_panel = view.querySelector('converse-muc-bottom-panel');
|
||||||
bottom_panel.onKeyDown(enter);
|
bottom_panel.onKeyDown(enter);
|
||||||
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
|
|
||||||
|
|
||||||
const modal = _converse.api.modal.get('converse-modtools-modal');
|
const modal = await u.waitUntil(() => _converse.api.modal.get('converse-modtools-modal'));
|
||||||
const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid});
|
const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid});
|
||||||
|
|
||||||
expect(modal.getAssignableAffiliations(occupant)).toEqual(['owner', 'admin', 'member', 'outcast', 'none']);
|
expect(modal.getAssignableAffiliations(occupant)).toEqual(['owner', 'admin', 'member', 'outcast', 'none']);
|
||||||
|
@ -1976,8 +1976,7 @@ describe("Groupchats", function () {
|
|||||||
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);
|
||||||
|
await u.waitUntil(() => view.querySelector('.open-invite-modal'));
|
||||||
expect(view.querySelector('.open-invite-modal')).not.toBe(null);
|
|
||||||
|
|
||||||
// Members can't invite if the room isn't open
|
// Members can't invite if the room isn't open
|
||||||
view.model.getOwnOccupant().set('affiliation', 'member');
|
view.model.getOwnOccupant().set('affiliation', 'member');
|
||||||
@ -2474,7 +2473,7 @@ describe("Groupchats", function () {
|
|||||||
expect(view.model.features.get('temporary')).toBe(true);
|
expect(view.model.features.get('temporary')).toBe(true);
|
||||||
expect(view.model.features.get('unmoderated')).toBe(true);
|
expect(view.model.features.get('unmoderated')).toBe(true);
|
||||||
expect(view.model.features.get('unsecured')).toBe(false);
|
expect(view.model.features.get('unsecured')).toBe(false);
|
||||||
expect(view.querySelector('.chatbox-title__text').textContent.trim()).toBe('Room');
|
await u.waitUntil(() => view.querySelector('.chatbox-title__text').textContent.trim() === 'Room');
|
||||||
|
|
||||||
view.querySelector('.configure-chatroom-button').click();
|
view.querySelector('.configure-chatroom-button').click();
|
||||||
|
|
||||||
|
@ -76,36 +76,11 @@ export default class ChatBottomPanel extends ElementView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emitFocused (ev) {
|
emitFocused (ev) {
|
||||||
const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));
|
_converse.chatboxviews.get(this.getAttribute('jid'))?.emitFocused(ev);
|
||||||
if (chatview.contains(document.activeElement) || chatview.contains(ev.relatedTarget)) {
|
|
||||||
// Something else in this chatbox was already focused
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Triggered when the focus has been moved to a particular chat.
|
|
||||||
* @event _converse#chatBoxFocused
|
|
||||||
* @type { _converse.ChatBoxView | _converse.ChatRoomView }
|
|
||||||
* @example _converse.api.listen.on('chatBoxFocused', (view, event) => { ... });
|
|
||||||
*/
|
|
||||||
api.trigger('chatBoxFocused', this, ev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emitBlurred (ev) {
|
emitBlurred (ev) {
|
||||||
const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));
|
_converse.chatboxviews.get(this.getAttribute('jid'))?.emitBlurred(ev);
|
||||||
if (!chatview) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (chatview.contains(document.activeElement) || chatview.contains(ev.relatedTarget)) {
|
|
||||||
// Something else in this chatbox is still focused
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Triggered when the focus has been removed from a particular chat.
|
|
||||||
* @event _converse#chatBoxBlurred
|
|
||||||
* @type { _converse.ChatBoxView | _converse.ChatRoomView }
|
|
||||||
* @example _converse.api.listen.on('chatBoxBlurred', (view, event) => { ... });
|
|
||||||
*/
|
|
||||||
api.trigger('chatBoxBlurred', this, ev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarOptions () { // eslint-disable-line class-methods-use-this
|
getToolbarOptions () { // eslint-disable-line class-methods-use-this
|
||||||
|
124
src/plugins/chatview/heading.js
Normal file
124
src/plugins/chatview/heading.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import UserDetailsModal from 'modals/user-details.js';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import tpl_chatbox_head from 'templates/chatbox_head.js';
|
||||||
|
import { ElementView } from '@converse/skeletor/src/element.js';
|
||||||
|
import { __ } from 'i18n';
|
||||||
|
import { _converse, api } from "@converse/headless/core";
|
||||||
|
import { getHeadingDropdownItem, getHeadingStandaloneButton } from 'plugins/chatview/utils.js';
|
||||||
|
import { render } from 'lit-html';
|
||||||
|
|
||||||
|
|
||||||
|
export default class ChatHeading extends ElementView {
|
||||||
|
|
||||||
|
async render () {
|
||||||
|
const tpl = await this.generateHeadingTemplate();
|
||||||
|
render(tpl, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback () {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.model = _converse.chatboxes.get(this.getAttribute('jid'));
|
||||||
|
this.debouncedRender = debounce(this.render, 100);
|
||||||
|
this.listenTo(this.model, 'vcard:change', this.debouncedRender);
|
||||||
|
if (this.model.contact) {
|
||||||
|
this.listenTo(this.model.contact, 'destroy', this.debouncedRender);
|
||||||
|
}
|
||||||
|
this.model.rosterContactAdded?.then(() => {
|
||||||
|
this.listenTo(this.model.contact, 'change:nickname', this.debouncedRender);
|
||||||
|
this.debouncedRender();
|
||||||
|
});
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
showUserDetailsModal (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
api.modal.show(UserDetailsModal, { model: this.model }, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
close () {
|
||||||
|
_converse.chatboxviews.get(this.getAttribute('jid'))?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of objects which represent buttons for the chat's header.
|
||||||
|
* @async
|
||||||
|
* @emits _converse#getHeadingButtons
|
||||||
|
*/
|
||||||
|
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': api.settings.get('view_mode') === 'overlayed'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (!api.settings.get('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': api.settings.get('view_mode') === 'overlayed'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* *Hook* which allows plugins to add more buttons to a chat's heading.
|
||||||
|
* @event _converse#getHeadingButtons
|
||||||
|
* @example
|
||||||
|
* api.listen.on('getHeadingButtons', (view, buttons) => {
|
||||||
|
* buttons.push({
|
||||||
|
* 'i18n_title': __('Foo'),
|
||||||
|
* 'i18n_text': __('Foo Bar'),
|
||||||
|
* 'handler': ev => alert('Foo!'),
|
||||||
|
* 'a_class': 'toggle-foo',
|
||||||
|
* 'icon_class': 'fa-foo',
|
||||||
|
* 'name': 'foo'
|
||||||
|
* });
|
||||||
|
* return buttons;
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));
|
||||||
|
if (chatview) {
|
||||||
|
return _converse.api.hook('getHeadingButtons', chatview, buttons);
|
||||||
|
} else {
|
||||||
|
return buttons; // Happens during tests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateHeadingTemplate () {
|
||||||
|
const vcard = this.model?.vcard;
|
||||||
|
const vcard_json = vcard ? vcard.toJSON() : {};
|
||||||
|
const i18n_profile = __("The User's Profile Image");
|
||||||
|
const avatar_data = Object.assign(
|
||||||
|
{
|
||||||
|
'alt_text': i18n_profile,
|
||||||
|
'extra_classes': '',
|
||||||
|
'height': 40,
|
||||||
|
'width': 40
|
||||||
|
},
|
||||||
|
vcard_json
|
||||||
|
);
|
||||||
|
const heading_btns = await 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(this.model.toJSON(), {
|
||||||
|
avatar_data,
|
||||||
|
'display_name': this.model.getDisplayName(),
|
||||||
|
'dropdown_btns': dropdown_btns.map(b => getHeadingDropdownItem(b)),
|
||||||
|
'showUserDetailsModal': ev => this.showUserDetailsModal(ev),
|
||||||
|
'standalone_btns': standalone_btns.map(b => getHeadingStandaloneButton(b))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
api.elements.define('converse-chat-heading', ChatHeading);
|
23
src/plugins/chatview/utils.js
Normal file
23
src/plugins/chatview/utils.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { html } from 'lit-html';
|
||||||
|
|
||||||
|
|
||||||
|
export async function 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
|
||||||
|
>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function 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>
|
||||||
|
`;
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
|
import 'plugins/chatview/heading.js';
|
||||||
import 'plugins/chatview/bottom_panel.js';
|
import 'plugins/chatview/bottom_panel.js';
|
||||||
import BaseChatView from 'shared/chat/baseview.js';
|
import BaseChatView from 'shared/chat/baseview.js';
|
||||||
import UserDetailsModal from 'modals/user-details.js';
|
|
||||||
import tpl_chatbox from 'templates/chatbox.js';
|
import tpl_chatbox from 'templates/chatbox.js';
|
||||||
import tpl_chatbox_head from 'templates/chatbox_head.js';
|
|
||||||
import { __ } from 'i18n';
|
import { __ } from 'i18n';
|
||||||
import { _converse, api, converse } from '@converse/headless/core';
|
import { _converse, api, converse } from '@converse/headless/core';
|
||||||
import { render } from 'lit-html';
|
import { render } from 'lit-html';
|
||||||
@ -36,19 +35,7 @@ export default class ChatView extends BaseChatView {
|
|||||||
this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
|
this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
|
||||||
this.listenTo(this.model, 'change:hidden', () => !this.model.get('hidden') && this.afterShown());
|
this.listenTo(this.model, 'change:hidden', () => !this.model.get('hidden') && this.afterShown());
|
||||||
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
|
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
|
||||||
this.listenTo(this.model, 'vcard:change', this.renderHeading);
|
|
||||||
this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);
|
this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);
|
||||||
|
|
||||||
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.listenTo(this.model.presence, 'change:show', this.onPresenceChanged);
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
@ -74,7 +61,6 @@ export default class ChatView extends BaseChatView {
|
|||||||
render(result, this);
|
render(result, this);
|
||||||
this.content = this.querySelector('.chat-content');
|
this.content = this.querySelector('.chat-content');
|
||||||
this.help_container = this.querySelector('.chat-content__help');
|
this.help_container = this.querySelector('.chat-content__help');
|
||||||
this.renderHeading();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,87 +79,6 @@ export default class ChatView extends BaseChatView {
|
|||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
showUserDetailsModal (ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
api.modal.show(UserDetailsModal, { model: this.model }, ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateHeadingTemplate () {
|
|
||||||
const vcard = this.model?.vcard;
|
|
||||||
const vcard_json = vcard ? vcard.toJSON() : {};
|
|
||||||
const i18n_profile = __("The User's Profile Image");
|
|
||||||
const avatar_data = Object.assign(
|
|
||||||
{
|
|
||||||
'alt_text': i18n_profile,
|
|
||||||
'extra_classes': '',
|
|
||||||
'height': 40,
|
|
||||||
'width': 40
|
|
||||||
},
|
|
||||||
vcard_json
|
|
||||||
);
|
|
||||||
const heading_btns = await 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(this.model.toJSON(), {
|
|
||||||
avatar_data,
|
|
||||||
'display_name': this.model.getDisplayName(),
|
|
||||||
'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)),
|
|
||||||
'showUserDetailsModal': ev => this.showUserDetailsModal(ev),
|
|
||||||
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b))
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of objects which represent buttons for the chat's header.
|
|
||||||
* @async
|
|
||||||
* @emits _converse#getHeadingButtons
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatBoxView#getHeadingButtons
|
|
||||||
*/
|
|
||||||
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': api.settings.get('view_mode') === 'overlayed'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
if (!api.settings.get('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': api.settings.get('view_mode') === 'overlayed'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* *Hook* which allows plugins to add more buttons to a chat's heading.
|
|
||||||
* @event _converse#getHeadingButtons
|
|
||||||
* @example
|
|
||||||
* api.listen.on('getHeadingButtons', (view, buttons) => {
|
|
||||||
* buttons.push({
|
|
||||||
* 'i18n_title': __('Foo'),
|
|
||||||
* 'i18n_text': __('Foo Bar'),
|
|
||||||
* 'handler': ev => alert('Foo!'),
|
|
||||||
* 'a_class': 'toggle-foo',
|
|
||||||
* 'icon_class': 'fa-foo',
|
|
||||||
* 'name': 'foo'
|
|
||||||
* });
|
|
||||||
* return buttons;
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
return _converse.api.hook('getHeadingButtons', this, buttons);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a message element, determine wether it should be
|
* Given a message element, determine wether it should be
|
||||||
* marked as a followup message to the previous element.
|
* marked as a followup message to the previous element.
|
||||||
|
54
src/plugins/headlines-view/heading.js
Normal file
54
src/plugins/headlines-view/heading.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import ChatHeading from 'plugins/chatview/heading.js';
|
||||||
|
import tpl_chat_head from './templates/chat-head.js';
|
||||||
|
import { __ } from 'i18n';
|
||||||
|
import { _converse, api } from "@converse/headless/core";
|
||||||
|
import { getHeadingDropdownItem, getHeadingStandaloneButton } from 'plugins/chatview/utils.js';
|
||||||
|
|
||||||
|
|
||||||
|
export default class HeadlinesHeading extends ChatHeading {
|
||||||
|
|
||||||
|
async connectedCallback () {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.model = _converse.chatboxes.get(this.getAttribute('jid'));
|
||||||
|
await this.model.initialized;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateHeadingTemplate () {
|
||||||
|
const heading_btns = await this.getHeadingButtons();
|
||||||
|
const standalone_btns = heading_btns.filter(b => b.standalone);
|
||||||
|
const dropdown_btns = heading_btns.filter(b => !b.standalone);
|
||||||
|
return tpl_chat_head(
|
||||||
|
Object.assign(this.model.toJSON(), {
|
||||||
|
'display_name': this.model.getDisplayName(),
|
||||||
|
'dropdown_btns': dropdown_btns.map(b => getHeadingDropdownItem(b)),
|
||||||
|
'standalone_btns': standalone_btns.map(b => getHeadingStandaloneButton(b))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of objects which represent buttons for the headlines header.
|
||||||
|
* @async
|
||||||
|
* @emits _converse#getHeadingButtons
|
||||||
|
* @method HeadlinesHeading#getHeadingButtons
|
||||||
|
*/
|
||||||
|
getHeadingButtons () {
|
||||||
|
const buttons = [];
|
||||||
|
if (!api.settings.get('singleton')) {
|
||||||
|
buttons.push({
|
||||||
|
'a_class': 'close-chatbox-button',
|
||||||
|
'handler': ev => this.close(ev),
|
||||||
|
'i18n_text': __('Close'),
|
||||||
|
'i18n_title': __('Close these announcements'),
|
||||||
|
'icon_class': 'fa-times',
|
||||||
|
'name': 'close',
|
||||||
|
'standalone': api.settings.get('view_mode') === 'overlayed'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _converse.api.hook('getHeadingButtons', this, buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
api.elements.define('converse-headlines-heading', HeadlinesHeading);
|
19
src/plugins/headlines-view/templates/headlines.js
Normal file
19
src/plugins/headlines-view/templates/headlines.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import '../heading.js';
|
||||||
|
import { html } from "lit-html";
|
||||||
|
|
||||||
|
export default (o) => html`
|
||||||
|
<div class="flyout box-flyout">
|
||||||
|
<converse-dragresize></converse-dragresize>
|
||||||
|
<converse-headlines-heading jid="${o.jid}" class="chat-head chat-head-chatbox row no-gutters"></converse-headlines-heading>
|
||||||
|
<div class="chat-body">
|
||||||
|
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
||||||
|
<converse-chat-content
|
||||||
|
class="chat-content__messages"
|
||||||
|
jid="${o.jid}"
|
||||||
|
@scroll=${o.markScrolled}></converse-chat-content>
|
||||||
|
|
||||||
|
<div class="chat-content__help"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -1,7 +1,5 @@
|
|||||||
import BaseChatView from 'shared/chat/baseview.js';
|
import BaseChatView from 'shared/chat/baseview.js';
|
||||||
import tpl_chatbox from 'templates/chatbox.js';
|
import tpl_headlines from './templates/headlines.js';
|
||||||
import tpl_chat_head from './templates/chat-head.js';
|
|
||||||
import { __ } from 'i18n';
|
|
||||||
import { _converse, api } from '@converse/headless/core';
|
import { _converse, api } from '@converse/headless/core';
|
||||||
import { render } from 'lit-html';
|
import { render } from 'lit-html';
|
||||||
|
|
||||||
@ -47,7 +45,7 @@ class HeadlinesView extends BaseChatView {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
this.setAttribute('id', this.model.get('box_id'));
|
this.setAttribute('id', this.model.get('box_id'));
|
||||||
const result = tpl_chatbox(
|
const result = tpl_headlines(
|
||||||
Object.assign(this.model.toJSON(), {
|
Object.assign(this.model.toJSON(), {
|
||||||
show_send_button: false,
|
show_send_button: false,
|
||||||
show_toolbar: false,
|
show_toolbar: false,
|
||||||
@ -55,7 +53,6 @@ class HeadlinesView extends BaseChatView {
|
|||||||
);
|
);
|
||||||
render(result, this);
|
render(result, this);
|
||||||
this.content = this.querySelector('.chat-content');
|
this.content = this.querySelector('.chat-content');
|
||||||
this.renderHeading();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,47 +73,6 @@ class HeadlinesView extends BaseChatView {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateHeadingTemplate () {
|
|
||||||
const heading_btns = await this.getHeadingButtons();
|
|
||||||
const standalone_btns = heading_btns.filter(b => b.standalone);
|
|
||||||
const dropdown_btns = heading_btns.filter(b => !b.standalone);
|
|
||||||
return tpl_chat_head(
|
|
||||||
Object.assign(this.model.toJSON(), {
|
|
||||||
'display_name': this.model.getDisplayName(),
|
|
||||||
'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)),
|
|
||||||
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b))
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of objects which represent buttons for the headlines header.
|
|
||||||
* @async
|
|
||||||
* @emits _converse#getHeadingButtons
|
|
||||||
* @private
|
|
||||||
* @method _converse.HeadlinesBoxView#getHeadingButtons
|
|
||||||
*/
|
|
||||||
getHeadingButtons () {
|
|
||||||
const buttons = [];
|
|
||||||
if (!api.settings.get('singleton')) {
|
|
||||||
buttons.push({
|
|
||||||
'a_class': 'close-chatbox-button',
|
|
||||||
'handler': ev => this.close(ev),
|
|
||||||
'i18n_text': __('Close'),
|
|
||||||
'i18n_title': __('Close these announcements'),
|
|
||||||
'icon_class': 'fa-times',
|
|
||||||
'name': 'close',
|
|
||||||
'standalone': api.settings.get('view_mode') === 'overlayed'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return _converse.api.hook('getHeadingButtons', this, buttons);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override to avoid the methods in converse-chatview
|
|
||||||
renderMessageForm () { // eslint-disable-line class-methods-use-this
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
afterShown () { // eslint-disable-line class-methods-use-this
|
afterShown () { // eslint-disable-line class-methods-use-this
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
191
src/plugins/muc-views/heading.js
Normal file
191
src/plugins/muc-views/heading.js
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import ChatHeading from 'plugins/chatview/heading.js';
|
||||||
|
import MUCInviteModal from 'modals/muc-invite.js';
|
||||||
|
import RoomDetailsModal from 'modals/muc-details.js';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import tpl_muc_head from './templates/muc_head.js';
|
||||||
|
import { Model } from '@converse/skeletor/src/model.js';
|
||||||
|
import { __ } from 'i18n';
|
||||||
|
import { _converse, api, converse } from "@converse/headless/core";
|
||||||
|
import { getHeadingDropdownItem, getHeadingStandaloneButton } from 'plugins/chatview/utils.js';
|
||||||
|
|
||||||
|
|
||||||
|
export default class MUCHeading extends ChatHeading {
|
||||||
|
|
||||||
|
async connectedCallback () {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.model = _converse.chatboxes.get(this.getAttribute('jid'));
|
||||||
|
this.debouncedRender = debounce(this.render, 100);
|
||||||
|
this.listenTo(this.model, 'change', this.debouncedRender);
|
||||||
|
|
||||||
|
const user_settings = await _converse.api.user.settings.getModel();
|
||||||
|
this.listenTo(user_settings, 'change:mucs_with_hidden_subject', this.debouncedRender);
|
||||||
|
|
||||||
|
await this.model.initialized;
|
||||||
|
this.listenTo(this.model.features, 'change:open', this.debouncedRender);
|
||||||
|
this.model.occupants.forEach(o => this.onOccupantAdded(o));
|
||||||
|
this.listenTo(this.model.occupants, 'add', this.onOccupantAdded);
|
||||||
|
this.listenTo(this.model.occupants, 'change:affiliation', this.onOccupantAffiliationChanged);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
onOccupantAdded (occupant) {
|
||||||
|
if (occupant.get('jid') === _converse.bare_jid) {
|
||||||
|
this.debouncedRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOccupantAffiliationChanged (occupant) {
|
||||||
|
if (occupant.get('jid') === _converse.bare_jid) {
|
||||||
|
this.debouncedRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showRoomDetailsModal (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
showInviteModal (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
api.modal.show(MUCInviteModal, { 'model': new Model(), 'chatroomview': this }, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTopic (ev) {
|
||||||
|
ev?.preventDefault?.();
|
||||||
|
this.model.toggleSubjectHiddenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndRenderConfigurationForm () {
|
||||||
|
_converse.chatboxviews.get(this.getAttribute('jid'))?.getAndRenderConfigurationForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
showModeratorToolsModal () {
|
||||||
|
_converse.chatboxviews.get(this.getAttribute('jid'))?.showModeratorToolsModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
_converse.chatboxviews.get(this.getAttribute('jid'))?.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of objects which represent buttons for the groupchat header.
|
||||||
|
* @emits _converse#getHeadingButtons
|
||||||
|
*/
|
||||||
|
getHeadingButtons (subject_hidden) {
|
||||||
|
const buttons = [];
|
||||||
|
buttons.push({
|
||||||
|
'i18n_text': __('Details'),
|
||||||
|
'i18n_title': __('Show more information about this groupchat'),
|
||||||
|
'handler': ev => this.showRoomDetailsModal(ev),
|
||||||
|
'a_class': 'show-muc-details-modal',
|
||||||
|
'icon_class': 'fa-info-circle',
|
||||||
|
'name': 'details'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.model.getOwnAffiliation() === 'owner') {
|
||||||
|
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.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'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subject = this.model.get('subject');
|
||||||
|
if (subject && subject.text) {
|
||||||
|
buttons.push({
|
||||||
|
'i18n_text': subject_hidden ? __('Show topic') : __('Hide topic'),
|
||||||
|
'i18n_title': subject_hidden
|
||||||
|
? __('Show the topic message in the heading')
|
||||||
|
: __('Hide the topic in the heading'),
|
||||||
|
'handler': ev => this.toggleTopic(ev),
|
||||||
|
'a_class': 'hide-topic',
|
||||||
|
'icon_class': 'fa-minus-square',
|
||||||
|
'name': 'toggle-topic'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const conn_status = this.model.session.get('connection_status');
|
||||||
|
if (conn_status === converse.ROOMSTATUS.ENTERED) {
|
||||||
|
const allowed_commands = this.model.getAllowedCommands();
|
||||||
|
if (allowed_commands.includes('modtools')) {
|
||||||
|
buttons.push({
|
||||||
|
'i18n_text': __('Moderate'),
|
||||||
|
'i18n_title': __('Moderate this groupchat'),
|
||||||
|
'handler': () => this.showModeratorToolsModal(),
|
||||||
|
'a_class': 'moderate-chatroom-button',
|
||||||
|
'icon_class': 'fa-user-cog',
|
||||||
|
'name': 'moderate'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (allowed_commands.includes('destroy')) {
|
||||||
|
buttons.push({
|
||||||
|
'i18n_text': __('Destroy'),
|
||||||
|
'i18n_title': __('Remove this groupchat'),
|
||||||
|
'handler': ev => this.destroy(ev),
|
||||||
|
'a_class': 'destroy-chatroom-button',
|
||||||
|
'icon_class': 'fa-trash',
|
||||||
|
'name': 'destroy'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!api.settings.get('singleton')) {
|
||||||
|
buttons.push({
|
||||||
|
'i18n_text': __('Leave'),
|
||||||
|
'i18n_title': __('Leave and close this groupchat'),
|
||||||
|
'handler': async ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const messages = [__('Are you sure you want to leave this groupchat?')];
|
||||||
|
const result = await api.confirm(__('Confirm'), messages);
|
||||||
|
result && this.close(ev);
|
||||||
|
},
|
||||||
|
'a_class': 'close-chatbox-button',
|
||||||
|
'standalone': api.settings.get('view_mode') === 'overlayed',
|
||||||
|
'icon_class': 'fa-sign-out-alt',
|
||||||
|
'name': 'signout'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));
|
||||||
|
if (chatview) {
|
||||||
|
return _converse.api.hook('getHeadingButtons', chatview, buttons);
|
||||||
|
} else {
|
||||||
|
return buttons; // Happens during tests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the groupchat heading TemplateResult to be rendered.
|
||||||
|
*/
|
||||||
|
async generateHeadingTemplate () {
|
||||||
|
const subject_hidden = await this.model.isSubjectHidden();
|
||||||
|
const heading_btns = await this.getHeadingButtons(subject_hidden);
|
||||||
|
const standalone_btns = heading_btns.filter(b => b.standalone);
|
||||||
|
const dropdown_btns = heading_btns.filter(b => !b.standalone);
|
||||||
|
return tpl_muc_head(
|
||||||
|
Object.assign(this.model.toJSON(), {
|
||||||
|
_converse,
|
||||||
|
subject_hidden,
|
||||||
|
'dropdown_btns': dropdown_btns.map(b => getHeadingDropdownItem(b)),
|
||||||
|
'standalone_btns': standalone_btns.map(b => getHeadingStandaloneButton(b)),
|
||||||
|
'title': this.model.getDisplayName()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
api.elements.define('converse-muc-heading', MUCHeading);
|
@ -1,14 +1,10 @@
|
|||||||
import './bottom_panel.js';
|
|
||||||
import './config-form.js';
|
import './config-form.js';
|
||||||
import './password-form.js';
|
import './password-form.js';
|
||||||
import 'shared/autocomplete/index.js';
|
import 'shared/autocomplete/index.js';
|
||||||
import BaseChatView from 'shared/chat/baseview.js';
|
import BaseChatView from 'shared/chat/baseview.js';
|
||||||
import MUCInviteModal from 'modals/muc-invite.js';
|
|
||||||
import ModeratorToolsModal from 'modals/moderator-tools.js';
|
import ModeratorToolsModal from 'modals/moderator-tools.js';
|
||||||
import RoomDetailsModal from 'modals/muc-details.js';
|
|
||||||
import log from '@converse/headless/log';
|
import log from '@converse/headless/log';
|
||||||
import tpl_muc from './templates/muc.js';
|
import tpl_muc from './templates/muc.js';
|
||||||
import tpl_muc_head from './templates/muc_head.js';
|
|
||||||
import tpl_muc_destroyed from './templates/muc_destroyed.js';
|
import tpl_muc_destroyed from './templates/muc_destroyed.js';
|
||||||
import tpl_muc_disconnect from './templates/muc_disconnect.js';
|
import tpl_muc_disconnect from './templates/muc_disconnect.js';
|
||||||
import tpl_muc_nickname_form from './templates/muc_nickname_form.js';
|
import tpl_muc_nickname_form from './templates/muc_nickname_form.js';
|
||||||
@ -16,7 +12,6 @@ import tpl_spinner from 'templates/spinner.js';
|
|||||||
import { Model } from '@converse/skeletor/src/model.js';
|
import { Model } from '@converse/skeletor/src/model.js';
|
||||||
import { __ } from 'i18n';
|
import { __ } from 'i18n';
|
||||||
import { _converse, api, converse } from '@converse/headless/core';
|
import { _converse, api, converse } from '@converse/headless/core';
|
||||||
import { debounce } from 'lodash-es';
|
|
||||||
import { render } from 'lit-html';
|
import { render } from 'lit-html';
|
||||||
|
|
||||||
const { sizzle } = converse.env;
|
const { sizzle } = converse.env;
|
||||||
@ -53,14 +48,12 @@ export default class MUCView extends BaseChatView {
|
|||||||
this.initDebounced();
|
this.initDebounced();
|
||||||
|
|
||||||
this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
|
this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
|
||||||
this.listenTo(this.model, 'change', debounce(() => this.renderHeading(), 250));
|
|
||||||
this.listenTo(this.model, 'change:composing_spoiler', this.renderMessageForm);
|
this.listenTo(this.model, 'change:composing_spoiler', this.renderMessageForm);
|
||||||
this.listenTo(this.model, 'change:hidden', () => this.afterShown());
|
this.listenTo(this.model, 'change:hidden', () => this.afterShown());
|
||||||
this.listenTo(this.model, 'change:hidden_occupants', this.onSidebarToggle);
|
this.listenTo(this.model, 'change:hidden_occupants', this.onSidebarToggle);
|
||||||
this.listenTo(this.model, 'change:minimized', () => this.afterShown());
|
this.listenTo(this.model, 'change:minimized', () => this.afterShown());
|
||||||
this.listenTo(this.model, 'configurationNeeded', this.getAndRenderConfigurationForm);
|
this.listenTo(this.model, 'configurationNeeded', this.getAndRenderConfigurationForm);
|
||||||
this.listenTo(this.model, 'show', this.show);
|
this.listenTo(this.model, 'show', this.show);
|
||||||
this.listenTo(this.model.features, 'change:open', this.renderHeading);
|
|
||||||
this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);
|
this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);
|
||||||
this.listenTo(this.model.session, 'change:connection_status', this.renderAfterTransition);
|
this.listenTo(this.model.session, 'change:connection_status', this.renderAfterTransition);
|
||||||
|
|
||||||
@ -73,16 +66,9 @@ export default class MUCView extends BaseChatView {
|
|||||||
// Need to be registered after render has been called.
|
// Need to be registered after render has been called.
|
||||||
this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages);
|
this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages);
|
||||||
this.listenTo(this.model.messages, 'add', this.onMessageAdded);
|
this.listenTo(this.model.messages, 'add', this.onMessageAdded);
|
||||||
|
|
||||||
this.model.occupants.forEach(o => this.onOccupantAdded(o));
|
|
||||||
this.listenTo(this.model.occupants, 'add', this.onOccupantAdded);
|
|
||||||
this.listenTo(this.model.occupants, 'change:affiliation', this.onOccupantAffiliationChanged);
|
|
||||||
this.listenTo(this.model.occupants, 'change:show', this.showJoinOrLeaveNotification);
|
this.listenTo(this.model.occupants, 'change:show', this.showJoinOrLeaveNotification);
|
||||||
this.listenTo(this.model.occupants, 'remove', this.onOccupantRemoved);
|
this.listenTo(this.model.occupants, 'remove', this.onOccupantRemoved);
|
||||||
|
|
||||||
// Register later due to await
|
|
||||||
const user_settings = await _converse.api.user.settings.getModel();
|
|
||||||
this.listenTo(user_settings, 'change:mucs_with_hidden_subject', this.renderHeading);
|
|
||||||
this.renderAfterTransition();
|
this.renderAfterTransition();
|
||||||
this.model.maybeShow();
|
this.model.maybeShow();
|
||||||
this.scrollDown();
|
this.scrollDown();
|
||||||
@ -95,7 +81,7 @@ export default class MUCView extends BaseChatView {
|
|||||||
api.trigger('chatRoomViewInitialized', this);
|
api.trigger('chatRoomViewInitialized', this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async render () {
|
render () {
|
||||||
const sidebar_hidden = !this.shouldShowSidebar();
|
const sidebar_hidden = !this.shouldShowSidebar();
|
||||||
this.setAttribute('id', this.model.get('box_id'));
|
this.setAttribute('id', this.model.get('box_id'));
|
||||||
render(
|
render(
|
||||||
@ -126,7 +112,6 @@ export default class MUCView extends BaseChatView {
|
|||||||
// Render header as late as possible since it's async and we
|
// Render header as late as possible since it's async and we
|
||||||
// want the rest of the DOM elements to be available ASAP.
|
// want the rest of the DOM elements to be available ASAP.
|
||||||
// Otherwise e.g. this.notifications is not yet defined when accessed elsewhere.
|
// Otherwise e.g. this.notifications is not yet defined when accessed elsewhere.
|
||||||
await this.renderHeading();
|
|
||||||
!this.model.get('hidden') && this.show();
|
!this.model.get('hidden') && this.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,17 +144,6 @@ export default class MUCView extends BaseChatView {
|
|||||||
.filter(line => this.model.getAllowedCommands().some(c => line.startsWith(c + '<', 9)));
|
.filter(line => this.model.getAllowedCommands().some(c => line.startsWith(c + '<', 9)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the MUC heading if any relevant attributes have changed.
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatRoomView#renderHeading
|
|
||||||
* @param { _converse.ChatRoom } [item]
|
|
||||||
*/
|
|
||||||
async renderHeading () {
|
|
||||||
const tpl = await this.generateHeadingTemplate();
|
|
||||||
render(tpl, this.querySelector('.chat-head-chatroom'));
|
|
||||||
}
|
|
||||||
|
|
||||||
onStartResizeOccupants (ev) {
|
onStartResizeOccupants (ev) {
|
||||||
this.resizing = true;
|
this.resizing = true;
|
||||||
this.addEventListener('mousemove', this.onMouseMove);
|
this.addEventListener('mousemove', this.onMouseMove);
|
||||||
@ -266,11 +240,6 @@ export default class MUCView extends BaseChatView {
|
|||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
showRoomDetailsModal (ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
showChatStateNotification (message) {
|
showChatStateNotification (message) {
|
||||||
if (message.get('sender') === 'me') {
|
if (message.get('sender') === 'me') {
|
||||||
return;
|
return;
|
||||||
@ -289,139 +258,6 @@ export default class MUCView extends BaseChatView {
|
|||||||
this.querySelector('.occupants')?.setVisibility();
|
this.querySelector('.occupants')?.setVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
onOccupantAffiliationChanged (occupant) {
|
|
||||||
if (occupant.get('jid') === _converse.bare_jid) {
|
|
||||||
this.renderHeading();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of objects which represent buttons for the groupchat header.
|
|
||||||
* @emits _converse#getHeadingButtons
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatRoomView#getHeadingButtons
|
|
||||||
*/
|
|
||||||
getHeadingButtons (subject_hidden) {
|
|
||||||
const buttons = [];
|
|
||||||
buttons.push({
|
|
||||||
'i18n_text': __('Details'),
|
|
||||||
'i18n_title': __('Show more information about this groupchat'),
|
|
||||||
'handler': ev => this.showRoomDetailsModal(ev),
|
|
||||||
'a_class': 'show-muc-details-modal',
|
|
||||||
'icon_class': 'fa-info-circle',
|
|
||||||
'name': 'details'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.model.getOwnAffiliation() === 'owner') {
|
|
||||||
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.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'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const subject = this.model.get('subject');
|
|
||||||
if (subject && subject.text) {
|
|
||||||
buttons.push({
|
|
||||||
'i18n_text': subject_hidden ? __('Show topic') : __('Hide topic'),
|
|
||||||
'i18n_title': subject_hidden
|
|
||||||
? __('Show the topic message in the heading')
|
|
||||||
: __('Hide the topic in the heading'),
|
|
||||||
'handler': ev => this.toggleTopic(ev),
|
|
||||||
'a_class': 'hide-topic',
|
|
||||||
'icon_class': 'fa-minus-square',
|
|
||||||
'name': 'toggle-topic'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const conn_status = this.model.session.get('connection_status');
|
|
||||||
if (conn_status === converse.ROOMSTATUS.ENTERED) {
|
|
||||||
const allowed_commands = this.model.getAllowedCommands();
|
|
||||||
if (allowed_commands.includes('modtools')) {
|
|
||||||
buttons.push({
|
|
||||||
'i18n_text': __('Moderate'),
|
|
||||||
'i18n_title': __('Moderate this groupchat'),
|
|
||||||
'handler': () => this.showModeratorToolsModal(),
|
|
||||||
'a_class': 'moderate-chatroom-button',
|
|
||||||
'icon_class': 'fa-user-cog',
|
|
||||||
'name': 'moderate'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (allowed_commands.includes('destroy')) {
|
|
||||||
buttons.push({
|
|
||||||
'i18n_text': __('Destroy'),
|
|
||||||
'i18n_title': __('Remove this groupchat'),
|
|
||||||
'handler': ev => this.destroy(ev),
|
|
||||||
'a_class': 'destroy-chatroom-button',
|
|
||||||
'icon_class': 'fa-trash',
|
|
||||||
'name': 'destroy'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!api.settings.get('singleton')) {
|
|
||||||
buttons.push({
|
|
||||||
'i18n_text': __('Leave'),
|
|
||||||
'i18n_title': __('Leave and close this groupchat'),
|
|
||||||
'handler': async ev => {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const messages = [__('Are you sure you want to leave this groupchat?')];
|
|
||||||
const result = await api.confirm(__('Confirm'), messages);
|
|
||||||
result && this.close(ev);
|
|
||||||
},
|
|
||||||
'a_class': 'close-chatbox-button',
|
|
||||||
'standalone': api.settings.get('view_mode') === 'overlayed',
|
|
||||||
'icon_class': 'fa-sign-out-alt',
|
|
||||||
'name': 'signout'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return _converse.api.hook('getHeadingButtons', this, buttons);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the groupchat heading TemplateResult to be rendered.
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatRoomView#generateHeadingTemplate
|
|
||||||
*/
|
|
||||||
async generateHeadingTemplate () {
|
|
||||||
const subject_hidden = await this.model.isSubjectHidden();
|
|
||||||
const heading_btns = await this.getHeadingButtons(subject_hidden);
|
|
||||||
const standalone_btns = heading_btns.filter(b => b.standalone);
|
|
||||||
const dropdown_btns = heading_btns.filter(b => !b.standalone);
|
|
||||||
return tpl_muc_head(
|
|
||||||
Object.assign(this.model.toJSON(), {
|
|
||||||
_converse,
|
|
||||||
subject_hidden,
|
|
||||||
'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.toggleSubjectHiddenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
showInviteModal (ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
api.modal.show(MUCInviteModal, { 'model': new Model(), 'chatroomview': this }, ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback method that gets called after the chat has become visible.
|
* Callback method that gets called after the chat has become visible.
|
||||||
* @private
|
* @private
|
||||||
@ -660,11 +496,6 @@ export default class MUCView extends BaseChatView {
|
|||||||
u.showElement(container);
|
u.showElement(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
onOccupantAdded (occupant) {
|
|
||||||
if (occupant.get('jid') === _converse.bare_jid) {
|
|
||||||
this.renderHeading();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Working backwards, get today's most recent join/leave notification
|
* Working backwards, get today's most recent join/leave notification
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
|
import '../heading.js';
|
||||||
|
import '../bottom_panel.js';
|
||||||
import { html } from "lit-html";
|
import { html } from "lit-html";
|
||||||
|
|
||||||
export default (o) => html`
|
export default (o) => html`
|
||||||
<div class="flyout box-flyout">
|
<div class="flyout box-flyout">
|
||||||
<converse-dragresize></converse-dragresize>
|
<converse-dragresize></converse-dragresize>
|
||||||
<div class="chat-head chat-head-chatroom row no-gutters"></div>
|
<converse-muc-heading jid="${o.model.get('jid')}" class="chat-head chat-head-chatroom row no-gutters"></converse-muc-heading>
|
||||||
<div class="chat-body chatroom-body row no-gutters">
|
<div class="chat-body chatroom-body row no-gutters">
|
||||||
<div class="chat-area col">
|
<div class="chat-area col">
|
||||||
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
||||||
<converse-chat-content
|
<converse-chat-content
|
||||||
class="chat-content__messages"
|
class="chat-content__messages"
|
||||||
jid=${o.model.get('jid')}
|
jid="${o.model.get('jid')}"
|
||||||
@scroll=${o.markScrolled}></converse-chat-content>
|
@scroll=${o.markScrolled}></converse-chat-content>
|
||||||
|
|
||||||
<div class="chat-content__help"></div>
|
<div class="chat-content__help"></div>
|
||||||
</div>
|
</div>
|
||||||
<converse-muc-bottom-panel jid=${o.model.get('jid')} class="bottom-panel"></converse-muc-bottom-panel>
|
<converse-muc-bottom-panel jid="${o.model.get('jid')}" class="bottom-panel"></converse-muc-bottom-panel>
|
||||||
</div>
|
</div>
|
||||||
<div class="disconnect-container hidden"></div>
|
<div class="disconnect-container hidden"></div>
|
||||||
<converse-muc-sidebar class="occupants col-md-3 col-4 ${o.sidebar_hidden ? 'hidden' : ''}"
|
<converse-muc-sidebar class="occupants col-md-3 col-4 ${o.sidebar_hidden ? 'hidden' : ''}"
|
||||||
|
@ -14,11 +14,6 @@ export default class BaseChatView extends ElementView {
|
|||||||
this.debouncedScrollDown = debounce(this.scrollDown, 100);
|
this.debouncedScrollDown = debounce(this.scrollDown, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderHeading () {
|
|
||||||
const tpl = await this.generateHeadingTemplate();
|
|
||||||
render(tpl, this.querySelector('.chat-head-chatbox'));
|
|
||||||
}
|
|
||||||
|
|
||||||
renderHelpMessages () {
|
renderHelpMessages () {
|
||||||
render(
|
render(
|
||||||
html`
|
html`
|
||||||
@ -34,18 +29,6 @@ export default class BaseChatView extends ElementView {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getHeadingStandaloneButton (promise_or_data) { // eslint-disable-line class-methods-use-this
|
|
||||||
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>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideNewMessagesIndicator () {
|
hideNewMessagesIndicator () {
|
||||||
const new_msgs_indicator = this.querySelector('.new-msgs-indicator');
|
const new_msgs_indicator = this.querySelector('.new-msgs-indicator');
|
||||||
if (new_msgs_indicator !== null) {
|
if (new_msgs_indicator !== null) {
|
||||||
@ -77,6 +60,34 @@ export default class BaseChatView extends ElementView {
|
|||||||
this.afterShown();
|
this.afterShown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitBlurred (ev) {
|
||||||
|
if (this.contains(document.activeElement) || this.contains(ev.relatedTarget)) {
|
||||||
|
// Something else in this chatbox is still focused
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Triggered when the focus has been removed from a particular chat.
|
||||||
|
* @event _converse#chatBoxBlurred
|
||||||
|
* @type { _converse.ChatBoxView | _converse.ChatRoomView }
|
||||||
|
* @example _converse.api.listen.on('chatBoxBlurred', (view, event) => { ... });
|
||||||
|
*/
|
||||||
|
api.trigger('chatBoxBlurred', this, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitFocused (ev) {
|
||||||
|
if (this.contains(ev.relatedTarget)) {
|
||||||
|
// Something else in this chatbox was already focused
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Triggered when the focus has been moved to a particular chat.
|
||||||
|
* @event _converse#chatBoxFocused
|
||||||
|
* @type { _converse.ChatBoxView | _converse.ChatRoomView }
|
||||||
|
* @example _converse.api.listen.on('chatBoxFocused', (view, event) => { ... });
|
||||||
|
*/
|
||||||
|
api.trigger('chatBoxFocused', this, ev);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll to the previously saved scrollTop position, or scroll
|
* Scroll to the previously saved scrollTop position, or scroll
|
||||||
* down if it wasn't set.
|
* down if it wasn't set.
|
||||||
@ -123,16 +134,6 @@ export default class BaseChatView extends ElementView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async getHeadingDropdownItem (promise_or_data) { // eslint-disable-line class-methods-use-this
|
|
||||||
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
|
|
||||||
>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
showNewMessagesIndicator () {
|
showNewMessagesIndicator () {
|
||||||
u.showElement(this.querySelector('.new-msgs-indicator'));
|
u.showElement(this.querySelector('.new-msgs-indicator'));
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,17 @@ import { html } from "lit-html";
|
|||||||
export default (o) => html`
|
export default (o) => html`
|
||||||
<div class="flyout box-flyout">
|
<div class="flyout box-flyout">
|
||||||
<converse-dragresize></converse-dragresize>
|
<converse-dragresize></converse-dragresize>
|
||||||
<div class="chat-head chat-head-chatbox row no-gutters"></div>
|
<converse-chat-heading jid="${o.jid}" class="chat-head chat-head-chatbox row no-gutters"></converse-chat-heading>
|
||||||
<div class="chat-body">
|
<div class="chat-body">
|
||||||
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
||||||
<converse-chat-content
|
<converse-chat-content
|
||||||
class="chat-content__messages"
|
class="chat-content__messages"
|
||||||
jid=${o.jid}
|
jid="${o.jid}"
|
||||||
@scroll=${o.markScrolled}></converse-chat-content>
|
@scroll=${o.markScrolled}></converse-chat-content>
|
||||||
|
|
||||||
<div class="chat-content__help"></div>
|
<div class="chat-content__help"></div>
|
||||||
</div>
|
</div>
|
||||||
<converse-chat-bottom-panel jid=${o.jid} class="bottom-panel"> </converse-chat-bottom-panel>
|
<converse-chat-bottom-panel jid="${o.jid}" class="bottom-panel"> </converse-chat-bottom-panel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
Loading…
Reference in New Issue
Block a user