Commit e8536ebc authored by JC Brand's avatar JC Brand

Move converse-muc-views plugin into own folder

parent 2b6c56f1
...@@ -22,7 +22,7 @@ import "./plugins/dragresize.js"; // Allows chat boxes to be resized b ...@@ -22,7 +22,7 @@ import "./plugins/dragresize.js"; // Allows chat boxes to be resized b
import "./plugins/fullscreen.js"; import "./plugins/fullscreen.js";
import "./plugins/mam-views.js"; import "./plugins/mam-views.js";
import "./plugins/minimize.js"; // Allows chat boxes to be minimized import "./plugins/minimize.js"; // Allows chat boxes to be minimized
import "./plugins/muc-views.js"; // Views related to MUC import "./plugins/muc-views/index.js"; // Views related to MUC
import "./plugins/headlines-view.js"; import "./plugins/headlines-view.js";
import "./plugins/notifications.js"; import "./plugins/notifications.js";
import "./plugins/omemo.js"; import "./plugins/omemo.js";
......
import { _converse, api } from "@converse/headless/core";
export default {
/**
* The "roomviews" namespace groups methods relevant to chatroom
* (aka groupchats) views.
*
* @namespace _converse.api.roomviews
* @memberOf _converse.api
*/
roomviews: {
/**
* Retrieves a groupchat (aka chatroom) view. The chat should already be open.
*
* @method _converse.api.roomviews.get
* @param {String|string[]} name - e.g. 'coven@conference.shakespeare.lit' or
* ['coven@conference.shakespeare.lit', 'cave@conference.shakespeare.lit']
* @returns {View} View representing the groupchat
*
* @example
* // To return a single view, provide the JID of the groupchat
* const view = _converse.api.roomviews.get('coven@conference.shakespeare.lit');
*
* @example
* // To return an array of views, provide an array of JIDs:
* const views = _converse.api.roomviews.get(['coven@conference.shakespeare.lit', 'cave@conference.shakespeare.lit']);
*
* @example
* // To return views of all open groupchats, call the method without any parameters::
* const views = _converse.api.roomviews.get();
*
*/
get (jids) {
if (Array.isArray(jids)) {
const views = api.chatviews.get(jids);
return views.filter(v => v.model.get('type') === _converse.CHATROOMS_TYPE)
} else {
const view = api.chatviews.get(jids);
if (view.model.get('type') === _converse.CHATROOMS_TYPE) {
return view;
} else {
return null;
}
}
},
/**
* Lets you close open chatrooms.
*
* You can call this method without any arguments to close
* all open chatrooms, or you can specify a single JID or
* an array of JIDs.
*
* @method _converse.api.roomviews.close
* @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s)
* @returns { Promise } - Promise which resolves once the views have been closed.
*/
close (jids) {
let views;
if (jids === undefined) {
views = _converse.chatboxviews;
} else if (typeof jids === 'string') {
views = [_converse.chatboxviews.get(jids)].filter(v => v);
} else if (Array.isArray(jids)) {
views = jids.map(jid => _converse.chatboxviews.get(jid));
}
return Promise.all(views.map(v => (v.is_chatroom && v.model && v.close())))
}
}
}
import log from "@converse/headless/log";
import tpl_muc_config_form from "templates/muc_config_form.js";
import { View } from '@converse/skeletor/src/view.js';
import { __ } from 'i18n';
import { api, converse } from "@converse/headless/core";
const { sizzle } = converse.env;
const u = converse.env.utils;
const MUCConfigForm = View.extend({
className: 'chatroom-form-container muc-config-form',
initialize (attrs) {
this.chatroomview = attrs.chatroomview;
this.listenTo(this.chatroomview.model.features, 'change:passwordprotected', this.render);
this.listenTo(this.chatroomview.model.features, 'change:config_stanza', this.render);
this.render();
},
toHTML () {
const stanza = u.toStanza(this.model.get('config_stanza'));
const whitelist = api.settings.get('roomconfig_whitelist');
let fields = sizzle('field', stanza);
if (whitelist.length) {
fields = fields.filter(f => whitelist.includes(f.getAttribute('var')));
}
const password_protected = this.model.features.get('passwordprotected');
const options = {
'new_password': !password_protected,
'fixed_username': this.model.get('jid')
};
return tpl_muc_config_form({
'closeConfigForm': ev => this.closeConfigForm(ev),
'fields': fields.map(f => u.xForm2webForm(f, stanza, options)),
'instructions': stanza.querySelector('instructions')?.textContent,
'submitConfigForm': ev => this.submitConfigForm(ev),
'title': stanza.querySelector('title')?.textContent
});
},
async submitConfigForm (ev) {
ev.preventDefault();
const inputs = sizzle(':input:not([type=button]):not([type=submit])', ev.target);
const config_array = inputs.map(u.webForm2xForm).filter(f => f);
try {
await this.model.sendConfiguration(config_array);
} catch (e) {
log.error(e);
const message =
__("Sorry, an error occurred while trying to submit the config form.") + " " +
__("Check your browser's developer console for details.");
api.alert('error', __('Error'), message);
}
await this.model.refreshDiscoInfo();
this.chatroomview.closeForm();
},
closeConfigForm (ev) {
ev.preventDefault();
this.chatroomview.closeForm();
}
});
export default MUCConfigForm
/**
* @module converse-muc-views
* @copyright 2020, the Converse.js contributors
* @description XEP-0045 Multi-User Chat Views
* @license Mozilla Public License (MPLv2)
*/
import '../../components/muc-sidebar';
import '../chatview/index.js';
import '../modal.js';
import '@converse/headless/utils/muc';
import ChatRoomViewMixin from './muc.js';
import MUCConfigForm from './config-form.js';
import MUCPasswordForm from './password-form.js';
import log from '@converse/headless/log';
import muc_api from './api.js';
import { RoomsPanel, RoomsPanelViewMixin } from './rooms-panel.js';
import { api, converse, _converse } from '@converse/headless/core';
const { Strophe } = converse.env;
function setMUCDomain (domain, controlboxview) {
controlboxview.getRoomsPanel().model.save('muc_domain', Strophe.getDomainFromJid(domain));
}
function setMUCDomainFromDisco (controlboxview) {
/* Check whether service discovery for the user's domain
* returned MUC information and use that to automatically
* set the MUC domain in the "Add groupchat" modal.
*/
function featureAdded (feature) {
if (!feature) {
return;
}
if (feature.get('var') === Strophe.NS.MUC) {
feature.entity.getIdentity('conference', 'text').then(identity => {
if (identity) {
setMUCDomain(feature.get('from'), controlboxview);
}
});
}
}
api.waitUntil('discoInitialized')
.then(() => {
api.listen.on('serviceDiscovered', featureAdded);
// Features could have been added before the controlbox was
// initialized. We're only interested in MUC
_converse.disco_entities.each(entity => featureAdded(entity.features.findWhere({ 'var': Strophe.NS.MUC })));
})
.catch(e => log.error(e));
}
function fetchAndSetMUCDomain (controlboxview) {
if (controlboxview.model.get('connected')) {
if (!controlboxview.getRoomsPanel().model.get('muc_domain')) {
if (api.settings.get('muc_domain') === undefined) {
setMUCDomainFromDisco(controlboxview);
} else {
setMUCDomain(api.settings.get('muc_domain'), controlboxview);
}
}
}
}
function openChatRoomFromURIClicked (ev) {
ev.preventDefault();
api.rooms.open(ev.target.href);
}
async function addView (model) {
const views = _converse.chatboxviews;
if (!views.get(model.get('id')) && model.get('type') === _converse.CHATROOMS_TYPE && model.isValid()) {
await model.initialized;
return views.add(model.get('id'), new _converse.ChatRoomView({ model }));
}
}
converse.plugins.add('converse-muc-views', {
/* Dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin. They are "optional" because they might not be
* available, in which case any overrides applicable to them will be
* ignored.
*
* NB: These plugins need to have already been loaded via require.js.
*
* It's possible to make these dependencies "non-optional".
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found.
*/
dependencies: ['converse-autocomplete', 'converse-modal', 'converse-controlbox', 'converse-chatview'],
overrides: {
ControlBoxView: {
renderControlBoxPane () {
this.__super__.renderControlBoxPane.apply(this, arguments);
if (api.settings.get('allow_muc')) {
this.renderRoomsPanel();
}
}
}
},
initialize () {
const { _converse } = this;
api.promises.add(['roomsPanelRendered']);
// Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
api.settings.extend({
'auto_list_rooms': false,
'cache_muc_messages': true,
'locked_muc_nickname': false,
'modtools_disable_query': [],
'modtools_disable_assign': false,
'muc_disable_slash_commands': false,
'muc_mention_autocomplete_filter': 'contains',
'muc_mention_autocomplete_min_chars': 0,
'muc_mention_autocomplete_show_avatar': true,
'muc_roomid_policy': null,
'muc_roomid_policy_hint': null,
'roomconfig_whitelist': [],
'show_retraction_warning': true,
'visible_toolbar_buttons': {
'toggle_occupants': true
}
});
_converse.MUCConfigForm = MUCConfigForm;
_converse.MUCPasswordForm = MUCPasswordForm;
_converse.ChatRoomView = _converse.ChatBoxView.extend(ChatRoomViewMixin);
_converse.RoomsPanel = RoomsPanel;
_converse.ControlBoxView && Object.assign(_converse.ControlBoxView.prototype, RoomsPanelViewMixin);
Object.assign(_converse.api, muc_api);
/************************ BEGIN Event Handlers ************************/
api.listen.on('chatBoxViewsInitialized', () => {
_converse.chatboxviews.delegate('click', 'a.open-chatroom', openChatRoomFromURIClicked);
_converse.chatboxes.on('add', addView);
});
api.listen.on('clearSession', () => {
const view = _converse.chatboxviews.get('controlbox');
if (view && view.roomspanel) {
view.roomspanel.model.destroy();
view.roomspanel.remove();
delete view.roomspanel;
}
});
api.listen.on('controlBoxInitialized', view => {
if (!api.settings.get('allow_muc')) {
return;
}
fetchAndSetMUCDomain(view);
view.model.on('change:connected', () => fetchAndSetMUCDomain(view));
});
/************************ END Event Handlers ************************/
}
});
/** import './config-form.js';
* @module converse-muc-views import './password-form.js';
* @copyright 2020, the Converse.js contributors import MUCInviteModal from 'modals/muc-invite.js';
* @description XEP-0045 Multi-User Chat Views import ModeratorToolsModal from 'modals/moderator-tools.js';
* @license Mozilla Public License (MPLv2) import OccupantModal from 'modals/occupant.js';
*/ import RoomDetailsModal from 'modals/muc-details.js';
import "../components/muc-sidebar"; import log from '@converse/headless/log';
import "./chatview/index.js"; import tpl_chatroom from 'templates/chatroom.js';
import "./modal.js"; import tpl_chatroom_head from 'templates/chatroom_head.js';
import "@converse/headless/utils/muc"; import tpl_muc_bottom_panel from 'templates/muc_bottom_panel.js';
import AddMUCModal from '../modals/add-muc.js'; import tpl_muc_destroyed from 'templates/muc_destroyed.js';
import MUCInviteModal from '../modals/muc-invite.js'; import tpl_muc_disconnect from 'templates/muc_disconnect.js';
import MUCListModal from '../modals/muc-list.js'; import tpl_muc_nickname_form from 'templates/muc_nickname_form.js';
import ModeratorToolsModal from "../modals/moderator-tools.js"; import tpl_spinner from 'templates/spinner.js';
import OccupantModal from '../modals/occupant.js';
import RoomDetailsModal from '../modals/muc-details.js';
import log from "@converse/headless/log";
import tpl_chatroom from "../templates/chatroom.js";
import tpl_chatroom_head from "../templates/chatroom_head.js";
import tpl_muc_bottom_panel from "../templates/muc_bottom_panel.js";
import tpl_muc_config_form from "../templates/muc_config_form.js";
import tpl_muc_destroyed from "../templates/muc_destroyed.js";
import tpl_muc_disconnect from "../templates/muc_disconnect.js";
import tpl_muc_nickname_form from "../templates/muc_nickname_form.js";
import tpl_muc_password_form from "../templates/muc_password_form.js";
import tpl_room_panel from "../templates/room_panel.js";
import tpl_spinner from "../templates/spinner.js";
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
import { View } from '@converse/skeletor/src/view.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 { debounce } from "lodash-es"; import { render } from 'lit-html';
import { render } from "lit-html";
const { Strophe, sizzle, $pres } = converse.env; const { Strophe, sizzle, $pres } = converse.env;
const u = converse.env.utils; const u = converse.env.utils;
...@@ -46,24 +32,22 @@ const COMMAND_TO_ROLE = { ...@@ -46,24 +32,22 @@ const COMMAND_TO_ROLE = {
'mute': 'visitor', 'mute': 'visitor',
'op': 'moderator', 'op': 'moderator',
'voice': 'participant' 'voice': 'participant'
} };
const COMMAND_TO_AFFILIATION = { const COMMAND_TO_AFFILIATION = {
'admin': 'admin', 'admin': 'admin',
'ban': 'outcast', 'ban': 'outcast',
'member': 'member', 'member': 'member',
'owner': 'owner', 'owner': 'owner',
'revoke': 'none' 'revoke': 'none'
} };
/** /**
* NativeView which renders a groupchat, based upon * Mixin which turns a ChatBoxView into a ChatRoomView
* { @link _converse.ChatBoxView } for normal one-on-one chat boxes. * @mixin
* @class
* @namespace _converse.ChatRoomView * @namespace _converse.ChatRoomView
* @memberOf _converse * @memberOf _converse
*/ */
export const ChatRoomView = _converse.ChatBoxView.extend({ const ChatRoomViewMixin = {
length: 300, length: 300,
tagName: 'div', tagName: 'div',
className: 'chatbox chatroom hidden', className: 'chatbox chatroom hidden',
...@@ -73,7 +57,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -73,7 +57,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'click .hide-occupants': 'hideOccupants', 'click .hide-occupants': 'hideOccupants',
'click .new-msgs-indicator': 'viewUnreadMessages', 'click .new-msgs-indicator': 'viewUnreadMessages',
// Arrow functions don't work here because you can't bind a different `this` param to them. // 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 .occupant-nick': function (ev) {
this.insertIntoTextArea(ev.target.textContent);
},
'click .send-button': 'onFormSubmitted', 'click .send-button': 'onFormSubmitted',
'dragover .chat-textarea': 'onDragOver', 'dragover .chat-textarea': 'onDragOver',
'drop .chat-textarea': 'onDrop', 'drop .chat-textarea': 'onDrop',
...@@ -82,15 +68,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -82,15 +68,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'keyup .chat-textarea': 'onKeyUp', 'keyup .chat-textarea': 'onKeyUp',
'mousedown .dragresize-occupants-left': 'onStartResizeOccupants', 'mousedown .dragresize-occupants-left': 'onStartResizeOccupants',
'paste .chat-textarea': 'onPaste', 'paste .chat-textarea': 'onPaste',
'submit .muc-nickname-form': 'submitNickname', 'submit .muc-nickname-form': 'submitNickname'
}, },
async initialize () { async initialize () {
this.initDebounced(); this.initDebounced();
this.listenTo(this.model, 'change', debounce(() => this.renderHeading(), 250)); 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', m => m.get('hidden') ? this.hide() : this.show()); this.listenTo(this.model, 'change:hidden', m => (m.get('hidden') ? this.hide() : this.show()));
this.listenTo(this.model, 'change:hidden_occupants', this.onSidebarToggle); this.listenTo(this.model, 'change:hidden_occupants', this.onSidebarToggle);
this.listenTo(this.model, 'configurationNeeded', this.getAndRenderConfigurationForm); this.listenTo(this.model, 'configurationNeeded', this.getAndRenderConfigurationForm);
this.listenTo(this.model, 'destroy', this.hide); this.listenTo(this.model, 'destroy', this.hide);
...@@ -142,16 +132,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -142,16 +132,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
async render () { async render () {
const sidebar_hidden = !this.shouldShowSidebar(); const sidebar_hidden = !this.shouldShowSidebar();
this.el.setAttribute('id', this.model.get('box_id')); this.el.setAttribute('id', this.model.get('box_id'));
render(tpl_chatroom({ render(
tpl_chatroom({
sidebar_hidden, sidebar_hidden,
'model': this.model, 'model': this.model,
'occupants': this.model.occupants, 'occupants': this.model.occupants,
'show_sidebar': !this.model.get('hidden_occupants') && 'show_sidebar':
!this.model.get('hidden_occupants') &&
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED, this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED,
'markScrolled': ev => this.markScrolled(ev), 'markScrolled': ev => this.markScrolled(ev),
'muc_show_logs_before_join': api.settings.get('muc_show_logs_before_join'), 'muc_show_logs_before_join': api.settings.get('muc_show_logs_before_join'),
'show_send_button': _converse.show_send_button, 'show_send_button': _converse.show_send_button
}), this.el); }),
this.el
);
this.notifications = this.el.querySelector('.chat-content__notifications'); this.notifications = this.el.querySelector('.chat-content__notifications');
this.content = this.el.querySelector('.chat-content'); this.content = this.el.querySelector('.chat-content');
...@@ -159,8 +153,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -159,8 +153,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
this.help_container = this.el.querySelector('.chat-content__help'); this.help_container = this.el.querySelector('.chat-content__help');
this.renderBottomPanel(); this.renderBottomPanel();
if (!api.settings.get('muc_show_logs_before_join') && if (
this.model.session.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { !api.settings.get('muc_show_logs_before_join') &&
this.model.session.get('connection_status') !== converse.ROOMSTATUS.ENTERED
) {
this.showSpinner(); this.showSpinner();
} }
// Render header as late as possible since it's async and we // Render header as late as possible since it's async and we
...@@ -173,17 +169,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -173,17 +169,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
getNotifications () { getNotifications () {
const actors_per_state = this.model.notifications.toJSON(); const actors_per_state = this.model.notifications.toJSON();
const role_changes = api.settings.get('muc_show_info_messages') const role_changes = api.settings
.get('muc_show_info_messages')
.filter(role_change => converse.MUC_ROLE_CHANGES_LIST.includes(role_change)); .filter(role_change => converse.MUC_ROLE_CHANGES_LIST.includes(role_change));
const join_leave_events = api.settings.get('muc_show_info_messages') const join_leave_events = api.settings
.get('muc_show_info_messages')
.filter(join_leave_event => converse.MUC_TRAFFIC_STATES_LIST.includes(join_leave_event)); .filter(join_leave_event => converse.MUC_TRAFFIC_STATES_LIST.includes(join_leave_event));
const states = [...converse.CHAT_STATES, ...join_leave_events, ...role_changes]; const states = [...converse.CHAT_STATES, ...join_leave_events, ...role_changes];
return states.reduce((result, state) => { return states.reduce((result, state) => {
const existing_actors = actors_per_state[state]; const existing_actors = actors_per_state[state];
if (!(existing_actors?.length)) { if (!existing_actors?.length) {
return result; return result;
} }
const actors = existing_actors.map(a => this.model.getOccupant(a)?.getDisplayName() || a); const actors = existing_actors.map(a => this.model.getOccupant(a)?.getDisplayName() || a);
...@@ -199,18 +197,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -199,18 +197,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} else if (state === 'exited') { } else if (state === 'exited') {
return `${result}${__('%1$s has left the groupchat', actors[0])}\n`; return `${result}${__('%1$s has left the groupchat', actors[0])}\n`;
} else if (state === 'op') { } else if (state === 'op') {
return `${result}${__("%1$s is now a moderator", actors[0])}\n`; return `${result}${__('%1$s is now a moderator', actors[0])}\n`;
} else if (state === 'deop') { } else if (state === 'deop') {
return `${result}${__("%1$s is no longer a moderator", actors[0])}\n`; return `${result}${__('%1$s is no longer a moderator', actors[0])}\n`;
} else if (state === 'voice') { } else if (state === 'voice') {
return `${result}${__("%1$s has been given a voice", actors[0])}\n`; return `${result}${__('%1$s has been given a voice', actors[0])}\n`;
} else if (state === 'mute') { } else if (state === 'mute') {
return `${result}${__("%1$s has been muted", actors[0])}\n`; return `${result}${__('%1$s has been muted', actors[0])}\n`;
} }
} else if (actors.length > 1) { } else if (actors.length > 1) {
let actors_str; let actors_str;
if (actors.length > 3) { if (actors.length > 3) {
actors_str = `${Array.from(actors).slice(0, 2).join(', ')} and others`; actors_str = `${Array.from(actors)
.slice(0, 2)
.join(', ')} and others`;
} else { } else {
const last_actor = actors.pop(); const last_actor = actors.pop();
actors_str = __('%1$s and %2$s', actors.join(', '), last_actor); actors_str = __('%1$s and %2$s', actors.join(', '), last_actor);
...@@ -227,13 +227,13 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -227,13 +227,13 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} else if (state === 'exited') { } else if (state === 'exited') {
return `${result}${__('%1$s have left the groupchat', actors_str)}\n`; return `${result}${__('%1$s have left the groupchat', actors_str)}\n`;
} else if (state === 'op') { } else if (state === 'op') {
return `${result}${__("%1$s are now moderators", actors[0])}\n`; return `${result}${__('%1$s are now moderators', actors[0])}\n`;
} else if (state === 'deop') { } else if (state === 'deop') {
return `${result}${__("%1$s are no longer moderators", actors[0])}\n`; return `${result}${__('%1$s are no longer moderators', actors[0])}\n`;
} else if (state === 'voice') { } else if (state === 'voice') {
return `${result}${__("%1$s have been given voices", actors[0])}\n`; return `${result}${__('%1$s have been given voices', actors[0])}\n`;
} else if (state === 'mute') { } else if (state === 'mute') {
return `${result}${__("%1$s have been muted", actors[0])}\n`; return `${result}${__('%1$s have been muted', actors[0])}\n`;
} }
} }
return result; return result;
...@@ -241,7 +241,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -241,7 +241,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}, },
getHelpMessages () { getHelpMessages () {
const setting = api.settings.get("muc_disable_slash_commands"); const setting = api.settings.get('muc_disable_slash_commands');
const disabled_commands = Array.isArray(setting) ? setting : []; const disabled_commands = Array.isArray(setting) ? setting : [];
return [ return [
`<strong>/admin</strong>: ${__("Change user's affiliation to admin")}`, `<strong>/admin</strong>: ${__("Change user's affiliation to admin")}`,
...@@ -259,13 +259,14 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -259,13 +259,14 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
`<strong>/nick</strong>: ${__('Change your nickname')}`, `<strong>/nick</strong>: ${__('Change your nickname')}`,
`<strong>/op</strong>: ${__('Grant moderator role to user')}`, `<strong>/op</strong>: ${__('Grant moderator role to user')}`,
`<strong>/owner</strong>: ${__('Grant ownership of this groupchat')}`, `<strong>/owner</strong>: ${__('Grant ownership of this groupchat')}`,
`<strong>/register</strong>: ${__("Register your nickname")}`, `<strong>/register</strong>: ${__('Register your nickname')}`,
`<strong>/revoke</strong>: ${__("Revoke the user's current affiliation")}`, `<strong>/revoke</strong>: ${__("Revoke the user's current affiliation")}`,
`<strong>/subject</strong>: ${__('Set groupchat subject')}`, `<strong>/subject</strong>: ${__('Set groupchat subject')}`,
`<strong>/topic</strong>: ${__('Set groupchat subject (alias for /subject)')}`, `<strong>/topic</strong>: ${__('Set groupchat subject (alias for /subject)')}`,
`<strong>/voice</strong>: ${__('Allow muted user to post messages')}` `<strong>/voice</strong>: ${__('Allow muted user to post messages')}`
].filter(line => disabled_commands.every(c => (!line.startsWith(c+'<', 9)))) ]
.filter(line => this.getAllowedCommands().some(c => line.startsWith(c+'<', 9))); .filter(line => disabled_commands.every(c => !line.startsWith(c + '<', 9)))
.filter(line => this.getAllowedCommands().some(c => line.startsWith(c + '<', 9)));
}, },
/** /**
...@@ -319,7 +320,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -319,7 +320,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
const sidebar_el = this.el.querySelector('converse-muc-sidebar'); const sidebar_el = this.el.querySelector('converse-muc-sidebar');
const element_position = sidebar_el.getBoundingClientRect(); const element_position = sidebar_el.getBoundingClientRect();
const occupants_width = this.calculateSidebarWidth(element_position, 0); const occupants_width = this.calculateSidebarWidth(element_position, 0);
const attrs = {occupants_width}; const attrs = { occupants_width };
_converse.connection.connected ? this.model.save(attrs) : this.model.set(attrs); _converse.connection.connected ? this.model.save(attrs) : this.model.set(attrs);
} }
}, },
...@@ -333,23 +334,23 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -333,23 +334,23 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
this.is_maximum = element_position.left > current_mouse_position; this.is_maximum = element_position.left > current_mouse_position;
} else { } else {
const occupants_width = this.calculateSidebarWidth(element_position, delta); const occupants_width = this.calculateSidebarWidth(element_position, delta);
sidebar_el.style.flex = "0 0 " + occupants_width + "px"; sidebar_el.style.flex = '0 0 ' + occupants_width + 'px';
} }
}, },
calculateSidebarWidth(element_position, delta) { calculateSidebarWidth (element_position, delta) {
let occupants_width = element_position.width + delta; let occupants_width = element_position.width + delta;
const room_width = this.el.clientWidth; const room_width = this.el.clientWidth;
// keeping display in boundaries // keeping display in boundaries
if (occupants_width < (room_width * 0.20)) { if (occupants_width < room_width * 0.2) {
// set pixel to 20% width // set pixel to 20% width
occupants_width = (room_width * 0.20); occupants_width = room_width * 0.2;
this.is_minimum = true; this.is_minimum = true;
} else if (occupants_width > (room_width * 0.75)) { } else if (occupants_width > room_width * 0.75) {
// set pixel to 75% width // set pixel to 75% width
occupants_width = (room_width * 0.75); occupants_width = room_width * 0.75;
this.is_maximum = true; this.is_maximum = true;
} else if ((room_width - occupants_width) < 250) { } else if (room_width - occupants_width < 250) {
// resize occupants if chat-area becomes smaller than 250px (min-width property set in css) // resize occupants if chat-area becomes smaller than 250px (min-width property set in css)
occupants_width = room_width - 250; occupants_width = room_width - 250;
this.is_maximum = true; this.is_maximum = true;
...@@ -361,35 +362,35 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -361,35 +362,35 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}, },
getAutoCompleteList () { getAutoCompleteList () {
return this.model.getAllKnownNicknames().map(nick => ({'label': nick, 'value': `@${nick}`})); return this.model.getAllKnownNicknames().map(nick => ({ 'label': nick, 'value': `@${nick}` }));
}, },
getAutoCompleteListItem(text, input) { getAutoCompleteListItem (text, input) {
input = input.trim(); input = input.trim();
const element = document.createElement("li"); const element = document.createElement('li');
element.setAttribute("aria-selected", "false"); element.setAttribute('aria-selected', 'false');
if (api.settings.get('muc_mention_autocomplete_show_avatar')) { if (api.settings.get('muc_mention_autocomplete_show_avatar')) {
const img = document.createElement("img"); const img = document.createElement('img');
let dataUri = "data:" + _converse.DEFAULT_IMAGE_TYPE + ";base64," + _converse.DEFAULT_IMAGE; let dataUri = 'data:' + _converse.DEFAULT_IMAGE_TYPE + ';base64,' + _converse.DEFAULT_IMAGE;
if (_converse.vcards) { if (_converse.vcards) {
const vcard = _converse.vcards.findWhere({'nickname': text}); const vcard = _converse.vcards.findWhere({ 'nickname': text });
if (vcard) dataUri = "data:" + vcard.get('image_type') + ";base64," + vcard.get('image'); if (vcard) dataUri = 'data:' + vcard.get('image_type') + ';base64,' + vcard.get('image');
} }
img.setAttribute("src", dataUri); img.setAttribute('src', dataUri);
img.setAttribute("width", "22"); img.setAttribute('width', '22');
img.setAttribute("class", "avatar avatar-autocomplete"); img.setAttribute('class', 'avatar avatar-autocomplete');
element.appendChild(img); element.appendChild(img);
} }
const regex = new RegExp("(" + input + ")", "ig"); const regex = new RegExp('(' + input + ')', 'ig');
const parts = input ? text.split(regex) : [text]; const parts = input ? text.split(regex) : [text];
parts.forEach(txt => { parts.forEach(txt => {
if (input && txt.match(regex)) { if (input && txt.match(regex)) {
const match = document.createElement("mark"); const match = document.createElement('mark');
match.textContent = txt; match.textContent = txt;
element.appendChild(match); element.appendChild(match);
} else { } else {
...@@ -407,10 +408,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -407,10 +408,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'min_chars': api.settings.get('muc_mention_autocomplete_min_chars'), 'min_chars': api.settings.get('muc_mention_autocomplete_min_chars'),
'match_current_word': true, 'match_current_word': true,
'list': () => this.getAutoCompleteList(), 'list': () => this.getAutoCompleteList(),
'filter': api.settings.get('muc_mention_autocomplete_filter') == 'contains' ? 'filter':
_converse.FILTER_CONTAINS : api.settings.get('muc_mention_autocomplete_filter') == 'contains'
_converse.FILTER_STARTSWITH, ? _converse.FILTER_CONTAINS
'ac_triggers': ["Tab", "@"], : _converse.FILTER_STARTSWITH,
'ac_triggers': ['Tab', '@'],
'include_triggers': [], 'include_triggers': [],
'item': this.getAutoCompleteListItem 'item': this.getAutoCompleteListItem
}); });
...@@ -442,10 +444,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -442,10 +444,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}, },
async onMessageRetractButtonClicked (message) { async onMessageRetractButtonClicked (message) {
const retraction_warning = const retraction_warning = __(
__("Be aware that other XMPP/Jabber clients (and servers) may "+ 'Be aware that other XMPP/Jabber clients (and servers) may ' +
"not yet support retractions and that this message may not "+ 'not yet support retractions and that this message may not ' +
"be removed everywhere."); 'be removed everywhere.'
);
if (message.mayBeRetracted()) { if (message.mayBeRetracted()) {
const messages = [__('Are you sure you want to retract this message?')]; const messages = [__('Are you sure you want to retract this message?')];
...@@ -457,7 +460,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -457,7 +460,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (message.get('sender') === 'me') { if (message.get('sender') === 'me') {
let messages = [__('Are you sure you want to retract this message?')]; let messages = [__('Are you sure you want to retract this message?')];
if (api.settings.get('show_retraction_warning')) { if (api.settings.get('show_retraction_warning')) {
messages = [messages[0], retraction_warning, messages[1]] messages = [messages[0], retraction_warning, messages[1]];
} }
!!(await api.confirm(__('Confirm'), messages)) && this.retractOtherMessage(message); !!(await api.confirm(__('Confirm'), messages)) && this.retractOtherMessage(message);
} else { } else {
...@@ -466,14 +469,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -466,14 +469,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
__('You may optionally include a message, explaining the reason for the retraction.') __('You may optionally include a message, explaining the reason for the retraction.')
]; ];
if (api.settings.get('show_retraction_warning')) { if (api.settings.get('show_retraction_warning')) {
messages = [messages[0], retraction_warning, messages[1]] messages = [messages[0], retraction_warning, messages[1]];
} }
const reason = await api.prompt( const reason = await api.prompt(__('Message Retraction'), messages, __('Optional reason'));
__('Message Retraction'), reason !== false && this.retractOtherMessage(message, reason);
messages,
__('Optional reason')
);
(reason !== false) && this.retractOtherMessage(message, reason);
} }
} else { } else {
const err_msg = __(`Sorry, you're not allowed to retract this message`); const err_msg = __(`Sorry, you're not allowed to retract this message`);
...@@ -510,20 +509,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -510,20 +509,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (modal) { if (modal) {
modal.model.set('affiliation', affiliation); modal.model.set('affiliation', affiliation);
} else { } else {
const model = new Model({'affiliation': affiliation}); const model = new Model({ 'affiliation': affiliation });
modal = api.modal.create(ModeratorToolsModal, {model, _converse, 'chatroomview': this}); modal = api.modal.create(ModeratorToolsModal, { model, _converse, 'chatroomview': this });
} }
modal.show(); modal.show();
}, },
showRoomDetailsModal (ev) { showRoomDetailsModal (ev) {
ev.preventDefault(); ev.preventDefault();
api.modal.show(RoomDetailsModal, {'model': this.model}, ev); api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
}, },
showOccupantDetailsModal (ev, message) { showOccupantDetailsModal (ev, message) {
ev.preventDefault(); ev.preventDefault();
api.modal.show(OccupantModal, {'model': message.occupant}, ev); api.modal.show(OccupantModal, { 'model': message.occupant }, ev);
}, },
showChatStateNotification (message) { showChatStateNotification (message) {
...@@ -534,8 +533,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -534,8 +533,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}, },
shouldShowSidebar () { shouldShowSidebar () {
return !this.model.get('hidden_occupants') && return (
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED; !this.model.get('hidden_occupants') &&
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED
);
}, },
onSidebarToggle () { onSidebarToggle () {
...@@ -598,9 +599,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -598,9 +599,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (subject && subject.text) { if (subject && subject.text) {
buttons.push({ buttons.push({
'i18n_text': subject_hidden ? __('Show topic') : __('Hide topic'), 'i18n_text': subject_hidden ? __('Show topic') : __('Hide topic'),
'i18n_title': subject_hidden ? 'i18n_title': subject_hidden
__('Show the topic message in the heading') : ? __('Show the topic message in the heading')
__('Hide the topic in the heading'), : __('Hide the topic in the heading'),
'handler': ev => this.toggleTopic(ev), 'handler': ev => this.toggleTopic(ev),
'a_class': 'hide-topic', 'a_class': 'hide-topic',
'icon_class': 'fa-minus-square', 'icon_class': 'fa-minus-square',
...@@ -608,7 +609,6 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -608,7 +609,6 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}); });
} }
const conn_status = this.model.session.get('connection_status'); const conn_status = this.model.session.get('connection_status');
if (conn_status === converse.ROOMSTATUS.ENTERED) { if (conn_status === converse.ROOMSTATUS.ENTERED) {
const allowed_commands = this.getAllowedCommands(); const allowed_commands = this.getAllowedCommands();
...@@ -634,7 +634,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -634,7 +634,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} }
} }
if (!api.settings.get("singleton")) { if (!api.settings.get('singleton')) {
buttons.push({ buttons.push({
'i18n_text': __('Leave'), 'i18n_text': __('Leave'),
'i18n_title': __('Leave and close this groupchat'), 'i18n_title': __('Leave and close this groupchat'),
...@@ -645,7 +645,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -645,7 +645,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
result && this.close(ev); result && this.close(ev);
}, },
'a_class': 'close-chatbox-button', 'a_class': 'close-chatbox-button',
'standalone': api.settings.get("view_mode") === 'overlayed', 'standalone': api.settings.get('view_mode') === 'overlayed',
'icon_class': 'fa-sign-out-alt', 'icon_class': 'fa-sign-out-alt',
'name': 'signout' 'name': 'signout'
}); });
...@@ -669,8 +669,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -669,8 +669,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
subject_hidden, subject_hidden,
'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)), 'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)),
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b)), 'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b)),
'title': this.model.getDisplayName(), 'title': this.model.getDisplayName()
})); })
);
}, },
toggleTopic () { toggleTopic () {
...@@ -679,10 +680,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -679,10 +680,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
showInviteModal (ev) { showInviteModal (ev) {
ev.preventDefault(); ev.preventDefault();
api.modal.show(MUCInviteModal, {'model': new Model(), 'chatroomview': this}, ev); 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
...@@ -718,13 +718,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -718,13 +718,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}, },
getToolbarOptions () { getToolbarOptions () {
return Object.assign( return Object.assign(_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), {
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), {
'is_groupchat': true, 'is_groupchat': true,
'label_hide_occupants': __('Hide the list of participants'), 'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants 'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
} });
);
}, },
/** /**
...@@ -734,7 +732,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -734,7 +732,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
*/ */
async close () { async close () {
this.hide(); this.hide();
if (_converse.router.history.getFragment() === "converse/room?jid="+this.model.get('jid')) { if (_converse.router.history.getFragment() === 'converse/room?jid=' + this.model.get('jid')) {
_converse.router.navigate(''); _converse.router.navigate('');
} }
await this.model.leave(); await this.model.leave();
...@@ -751,18 +749,18 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -751,18 +749,18 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
} }
this.model.save({'hidden_occupants': true}); this.model.save({ 'hidden_occupants': true });
this.scrollDown(); this.scrollDown();
}, },
verifyRoles (roles, occupant, show_error=true) { verifyRoles (roles, occupant, show_error = true) {
if (!Array.isArray(roles)) { if (!Array.isArray(roles)) {
throw new TypeError('roles must be an Array'); throw new TypeError('roles must be an Array');
} }
if (!roles.length) { if (!roles.length) {
return true; return true;
} }
occupant = occupant || this.model.occupants.findWhere({'jid': _converse.bare_jid}); occupant = occupant || this.model.occupants.findWhere({ 'jid': _converse.bare_jid });
if (occupant) { if (occupant) {
const role = occupant.get('role'); const role = occupant.get('role');
if (roles.includes(role)) { if (roles.includes(role)) {
...@@ -771,19 +769,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -771,19 +769,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} }
if (show_error) { if (show_error) {
const message = __('Forbidden: you do not have the necessary role in order to do that.'); const message = __('Forbidden: you do not have the necessary role in order to do that.');
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
} }
return false; return false;
}, },
verifyAffiliations (affiliations, occupant, show_error=true) { verifyAffiliations (affiliations, occupant, show_error = true) {
if (!Array.isArray(affiliations)) { if (!Array.isArray(affiliations)) {
throw new TypeError('affiliations must be an Array'); throw new TypeError('affiliations must be an Array');
} }
if (!affiliations.length) { if (!affiliations.length) {
return true; return true;
} }
occupant = occupant || this.model.occupants.findWhere({'jid': _converse.bare_jid}); occupant = occupant || this.model.occupants.findWhere({ 'jid': _converse.bare_jid });
if (occupant) { if (occupant) {
const a = occupant.get('affiliation'); const a = occupant.get('affiliation');
if (affiliations.includes(a)) { if (affiliations.includes(a)) {
...@@ -792,7 +790,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -792,7 +790,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} }
if (show_error) { if (show_error) {
const message = __('Forbidden: you do not have the necessary affiliation in order to do that.'); const message = __('Forbidden: you do not have the necessary affiliation in order to do that.');
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
} }
return false; return false;
}, },
...@@ -803,7 +801,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -803,7 +801,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.', 'Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.',
command command
); );
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
return false; return false;
} }
return true; return true;
...@@ -814,24 +812,24 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -814,24 +812,24 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
return args.trim(); return args.trim();
} }
if (!args.startsWith('@')) { if (!args.startsWith('@')) {
args = '@'+ args; args = '@' + args;
} }
const [text, references] = this.model.parseTextForReferences(args); // eslint-disable-line no-unused-vars const [text, references] = this.model.parseTextForReferences(args); // eslint-disable-line no-unused-vars
if (!references.length) { if (!references.length) {
const message = __("Error: couldn't find a groupchat participant based on your arguments"); const message = __("Error: couldn't find a groupchat participant based on your arguments");
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
return; return;
} }
if (references.length > 1) { if (references.length > 1) {
const message = __("Error: found multiple groupchat participant based on your arguments"); const message = __('Error: found multiple groupchat participant based on your arguments');
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
return; return;
} }
const nick_or_jid = references.pop().value; const nick_or_jid = references.pop().value;
const reason = args.split(nick_or_jid, 2)[1]; const reason = args.split(nick_or_jid, 2)[1];
if (reason && !reason.startsWith(' ')) { if (reason && !reason.startsWith(' ')) {
const message = __("Error: couldn't find a groupchat participant based on your arguments"); const message = __("Error: couldn't find a groupchat participant based on your arguments");
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
return; return;
} }
return nick_or_jid; return nick_or_jid;
...@@ -863,10 +861,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -863,10 +861,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
jid = nick_or_jid; jid = nick_or_jid;
} else { } else {
const message = __( const message = __(
"Couldn't find a participant with that nickname. "+ "Couldn't find a participant with that nickname. " + 'They might have left the groupchat.'
"They might have left the groupchat."
); );
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
return; return;
} }
} }
...@@ -874,16 +871,17 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -874,16 +871,17 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (occupant && api.settings.get('auto_register_muc_nickname')) { if (occupant && api.settings.get('auto_register_muc_nickname')) {
attrs['nick'] = occupant.get('nick'); attrs['nick'] = occupant.get('nick');
} }
this.model.setAffiliation(affiliation, [attrs]) this.model
.setAffiliation(affiliation, [attrs])
.then(() => this.model.occupants.fetchMembers()) .then(() => this.model.occupants.fetchMembers())
.catch(err => this.onCommandError(err)); .catch(err => this.onCommandError(err));
}, },
getReason (args) { getReason (args) {
return args.includes(',') ? args.slice(args.indexOf(',')+1).trim() : null; return args.includes(',') ? args.slice(args.indexOf(',') + 1).trim() : null;
}, },
setRole (command, args, required_affiliations=[], required_roles=[]) { setRole (command, args, required_affiliations = [], required_roles = []) {
/* Check that a command to change a groupchat user's role or /* Check that a command to change a groupchat user's role or
* affiliation has anough arguments. * affiliation has anough arguments.
*/ */
...@@ -911,9 +909,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -911,9 +909,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
onCommandError (err) { onCommandError (err) {
log.fatal(err); log.fatal(err);
const message = const message =
__("Sorry, an error happened while running the command.") + " " + __('Sorry, an error happened while running the command.') +
' ' +
__("Check your browser's developer console for details."); __("Check your browser's developer console for details.");
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
}, },
getAllowedCommands () { getAllowedCommands () {
...@@ -921,7 +920,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -921,7 +920,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (this.model.config.get('changesubject') || ['owner', 'admin'].includes(this.model.getOwnAffiliation())) { if (this.model.config.get('changesubject') || ['owner', 'admin'].includes(this.model.getOwnAffiliation())) {
allowed_commands = [...allowed_commands, ...['subject', 'topic']]; allowed_commands = [...allowed_commands, ...['subject', 'topic']];
} }
const occupant = this.model.occupants.findWhere({'jid': _converse.bare_jid}); const occupant = this.model.occupants.findWhere({ 'jid': _converse.bare_jid });
if (this.verifyAffiliations(['owner'], occupant, false)) { if (this.verifyAffiliations(['owner'], occupant, false)) {
allowed_commands = allowed_commands.concat(OWNER_COMMANDS).concat(ADMIN_COMMANDS); allowed_commands = allowed_commands.concat(OWNER_COMMANDS).concat(ADMIN_COMMANDS);
} else if (this.verifyAffiliations(['admin'], occupant, false)) { } else if (this.verifyAffiliations(['admin'], occupant, false)) {
...@@ -943,42 +942,48 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -943,42 +942,48 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
async destroy () { async destroy () {
const messages = [__('Are you sure you want to destroy this groupchat?')]; const messages = [__('Are you sure you want to destroy this groupchat?')];
let fields = [{ let fields = [
{
'name': 'challenge', 'name': 'challenge',
'label': __('Please enter the XMPP address of this groupchat to confirm'), 'label': __('Please enter the XMPP address of this groupchat to confirm'),
'challenge': this.model.get('jid'), 'challenge': this.model.get('jid'),
'placeholder': __('name@example.org'), 'placeholder': __('name@example.org'),
'required': true 'required': true
}, { },
{
'name': 'reason', 'name': 'reason',
'label': __('Optional reason for destroying this groupchat'), 'label': __('Optional reason for destroying this groupchat'),
'placeholder': __('Reason') 'placeholder': __('Reason')
}, { },
{
'name': 'newjid', 'name': 'newjid',
'label': __('Optional XMPP address for a new groupchat that replaces this one'), 'label': __('Optional XMPP address for a new groupchat that replaces this one'),
'placeholder': __('replacement@example.org') 'placeholder': __('replacement@example.org')
}]; }
];
try { try {
fields = await api.confirm(__('Confirm'), messages, fields); fields = await api.confirm(__('Confirm'), messages, fields);
const reason = fields.filter(f => f.name === 'reason').pop()?.value; const reason = fields.filter(f => f.name === 'reason').pop()?.value;
const newjid = fields.filter(f => f.name === 'newjid').pop()?.value; const newjid = fields.filter(f => f.name === 'newjid').pop()?.value;
return this.model.sendDestroyIQ(reason, newjid).then(() => this.close()) return this.model.sendDestroyIQ(reason, newjid).then(() => this.close());
} catch (e) { } catch (e) {
log.error(e); log.error(e);
} }
}, },
parseMessageForCommands (text) { parseMessageForCommands (text) {
if (api.settings.get('muc_disable_slash_commands') && if (
!Array.isArray(api.settings.get('muc_disable_slash_commands'))) { api.settings.get('muc_disable_slash_commands') &&
!Array.isArray(api.settings.get('muc_disable_slash_commands'))
) {
return _converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments); return _converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments);
} }
text = text.replace(/^\s*/, ""); text = text.replace(/^\s*/, '');
const command = (text.match(/^\/([a-zA-Z]*) ?/) || ['']).pop().toLowerCase(); const command = (text.match(/^\/([a-zA-Z]*) ?/) || ['']).pop().toLowerCase();
if (!command) { if (!command) {
return false; return false;
} }
const args = text.slice(('/'+command).length+1).trim(); const args = text.slice(('/' + command).length + 1).trim();
if (!this.getAllowedCommands().includes(command)) { if (!this.getAllowedCommands().includes(command)) {
return false; return false;
} }
...@@ -1014,9 +1019,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1014,9 +1019,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
break; break;
} }
case 'help': { case 'help': {
this.model.set({'show_help_messages': true}); this.model.set({ 'show_help_messages': true });
break; break;
} case 'kick': { }
case 'kick': {
this.setRole(command, args, [], ['moderator']); this.setRole(command, args, [], ['moderator']);
break; break;
} }
...@@ -1034,15 +1040,16 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1034,15 +1040,16 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} else if (args.length === 0) { } else if (args.length === 0) {
// e.g. Your nickname is "coolguy69" // e.g. Your nickname is "coolguy69"
const message = __('Your nickname is "%1$s"', this.model.get('nick')); const message = __('Your nickname is "%1$s"', this.model.get('nick'));
this.model.createMessage({message, 'type': 'error'}); this.model.createMessage({ message, 'type': 'error' });
} else { } else {
const jid = Strophe.getBareJidFromJid(this.model.get('jid')); const jid = Strophe.getBareJidFromJid(this.model.get('jid'));
api.send($pres({ api.send(
$pres({
from: _converse.connection.jid, from: _converse.connection.jid,
to: `${jid}/${args}`, to: `${jid}/${args}`,
id: u.getUniqueId() id: u.getUniqueId()
}).tree()); }).tree()
);
} }
break; break;
} }
...@@ -1061,7 +1068,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1061,7 +1068,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}); });
} else { } else {
this.model.registerNickname().then(err_msg => { this.model.registerNickname().then(err_msg => {
err_msg && this.model.createMessage({'message': err_msg, 'type': 'error'}); err_msg && this.model.createMessage({ 'message': err_msg, 'type': 'error' });
}); });
} }
break; break;
...@@ -1130,7 +1137,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1130,7 +1137,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
container.insertAdjacentElement('beforeend', form_el); container.insertAdjacentElement('beforeend', form_el);
} }
} }
u.safeSave(this.model.session, {'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED}); u.safeSave(this.model.session, { 'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED });
}, },
/** /**
...@@ -1160,7 +1167,8 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1160,7 +1167,8 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
getAndRenderConfigurationForm () { getAndRenderConfigurationForm () {
if (!this.config_form || !u.isVisible(this.config_form.el)) { if (!this.config_form || !u.isVisible(this.config_form.el)) {
this.showSpinner(); this.showSpinner();
this.model.fetchRoomConfiguration() this.model
.fetchRoomConfiguration()
.then(iq => this.renderConfigurationForm(iq)) .then(iq => this.renderConfigurationForm(iq))
.catch(e => log.error(e)); .catch(e => log.error(e));
} else { } else {
...@@ -1185,7 +1193,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1185,7 +1193,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'model': new Model({ 'model': new Model({
'validation_message': message 'validation_message': message
}), }),
'chatroomview': this, 'chatroomview': this
}); });
const container_el = this.el.querySelector('.chatroom-body'); const container_el = this.el.querySelector('.chatroom-body');
container_el.insertAdjacentElement('beforeend', this.password_form.el); container_el.insertAdjacentElement('beforeend', this.password_form.el);
...@@ -1265,7 +1273,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1265,7 +1273,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
* @param {string} nick * @param {string} nick
*/ */
getPreviousJoinOrLeaveNotification (el, nick) { getPreviousJoinOrLeaveNotification (el, nick) {
const today = (new Date()).toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
while (el !== null) { while (el !== null) {
if (!el.classList.contains('chat-info')) { if (!el.classList.contains('chat-info')) {
return; return;
...@@ -1277,10 +1285,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1277,10 +1285,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
return; return;
} }
const data = el?.dataset || {}; const data = el?.dataset || {};
if (data.join === nick || if (data.join === nick || data.leave === nick || data.leavejoin === nick || data.joinleave === nick) {
data.leave === nick ||
data.leavejoin === nick ||
data.joinleave === nick) {
return el; return el;
} }
el = el.previousElementSibling; el = el.previousElementSibling;
...@@ -1295,7 +1300,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1295,7 +1300,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
* @method _converse.ChatRoomView#renderAfterTransition * @method _converse.ChatRoomView#renderAfterTransition
*/ */
renderAfterTransition () { renderAfterTransition () {
const conn_status = this.model.session.get('connection_status') const conn_status = this.model.session.get('connection_status');
if (conn_status == converse.ROOMSTATUS.NICKNAME_REQUIRED) { if (conn_status == converse.ROOMSTATUS.NICKNAME_REQUIRED) {
this.renderNicknameForm(); this.renderNicknameForm();
} else if (conn_status == converse.ROOMSTATUS.PASSWORD_REQUIRED) { } else if (conn_status == converse.ROOMSTATUS.PASSWORD_REQUIRED) {
...@@ -1312,10 +1317,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1312,10 +1317,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
sizzle('.spinner', this.el).forEach(u.removeElement); sizzle('.spinner', this.el).forEach(u.removeElement);
this.hideChatRoomContents(); this.hideChatRoomContents();
const container_el = this.el.querySelector('.chatroom-body'); const container_el = this.el.querySelector('.chatroom-body');
container_el.insertAdjacentElement( container_el.insertAdjacentElement('afterbegin', u.getElementFromTemplateResult(tpl_spinner()));
'afterbegin',
u.getElementFromTemplateResult(tpl_spinner())
);
}, },
/** /**
...@@ -1333,371 +1335,6 @@ export const ChatRoomView = _converse.ChatBoxView.extend({ ...@@ -1333,371 +1335,6 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} }
return this; return this;
} }
}); };
/**
* View which renders MUC section of the control box.
* @class
* @namespace _converse.RoomsPanel
* @memberOf _converse
*/
export const RoomsPanel = View.extend({
tagName: 'div',
className: 'controlbox-section',
id: 'chatrooms',
events: {
'click a.controlbox-heading__btn.show-add-muc-modal': 'showAddRoomModal',
'click a.controlbox-heading__btn.show-list-muc-modal': 'showMUCListModal'
},
toHTML () {
return tpl_room_panel({
'heading_chatrooms': __('Groupchats'),
'title_new_room': __('Add a new groupchat'),
'title_list_rooms': __('Query for groupchats')
});
},
showAddRoomModal (ev) {
api.modal.show(AddMUCModal, {'model': this.model}, ev);
},
showMUCListModal(ev) {
api.modal.show(MUCListModal, {'model': this.model}, ev);
}
});
converse.plugins.add('converse-muc-views', {
/* Dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin. They are "optional" because they might not be
* available, in which case any overrides applicable to them will be
* ignored.
*
* NB: These plugins need to have already been loaded via require.js.
*
* It's possible to make these dependencies "non-optional".
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found.
*/
dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"],
overrides: {
ControlBoxView: {
renderControlBoxPane () {
this.__super__.renderControlBoxPane.apply(this, arguments);
if (api.settings.get('allow_muc')) {
this.renderRoomsPanel();
}
}
}
},
initialize () {
const { _converse } = this;
api.promises.add(['roomsPanelRendered']);
// Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
api.settings.extend({
'auto_list_rooms': false,
'cache_muc_messages': true,
'locked_muc_nickname': false,
'modtools_disable_query': [],
'modtools_disable_assign': false,
'muc_disable_slash_commands': false,
'muc_mention_autocomplete_filter': 'contains',
'muc_mention_autocomplete_min_chars': 0,
'muc_mention_autocomplete_show_avatar': true,
'muc_roomid_policy': null,
'muc_roomid_policy_hint': null,
'roomconfig_whitelist': [],
'show_retraction_warning': true,
'visible_toolbar_buttons': {
'toggle_occupants': true
}
});
_converse.ChatRoomView = ChatRoomView;
_converse.RoomsPanel = RoomsPanel;
const viewWithRoomsPanel = {
renderRoomsPanel () {
if (this.roomspanel && u.isInDOM(this.roomspanel.el)) {
return this.roomspanel;
}
const id = `converse.roomspanel${_converse.bare_jid}`;
this.roomspanel = new _converse.RoomsPanel({
'model': new (_converse.RoomsPanelModel.extend({
id,
'browserStorage': _converse.createStore(id)
}))()
});
this.roomspanel.model.fetch();
this.el.querySelector('.controlbox-pane').insertAdjacentElement(
'beforeEnd', this.roomspanel.render().el);
/**
* Triggered once the section of the { @link _converse.ControlBoxView }
* which shows gropuchats has been rendered.
* @event _converse#roomsPanelRendered
* @example _converse.api.listen.on('roomsPanelRendered', () => { ... });
*/
api.trigger('roomsPanelRendered');
return this.roomspanel;
},
getRoomsPanel () {
if (this.roomspanel && u.isInDOM(this.roomspanel.el)) {
return this.roomspanel;
} else {
return this.renderRoomsPanel();
}
}
}
if (_converse.ControlBoxView) {
Object.assign(_converse.ControlBoxView.prototype, viewWithRoomsPanel);
}
_converse.MUCConfigForm = View.extend({
className: 'chatroom-form-container muc-config-form',
initialize (attrs) {
this.chatroomview = attrs.chatroomview;
this.listenTo(this.chatroomview.model.features, 'change:passwordprotected', this.render);
this.listenTo(this.chatroomview.model.features, 'change:config_stanza', this.render);
this.render();
},
toHTML () {
const stanza = u.toStanza(this.model.get('config_stanza'));
const whitelist = api.settings.get('roomconfig_whitelist');
let fields = sizzle('field', stanza);
if (whitelist.length) {
fields = fields.filter(f => whitelist.includes(f.getAttribute('var')));
}
const password_protected = this.model.features.get('passwordprotected');
const options = {
'new_password': !password_protected,
'fixed_username': this.model.get('jid')
};
return tpl_muc_config_form({
'closeConfigForm': ev => this.closeConfigForm(ev),
'fields': fields.map(f => u.xForm2webForm(f, stanza, options)),
'instructions': stanza.querySelector('instructions')?.textContent,
'submitConfigForm': ev => this.submitConfigForm(ev),
'title': stanza.querySelector('title')?.textContent
});
},
async submitConfigForm (ev) {
ev.preventDefault();
const inputs = sizzle(':input:not([type=button]):not([type=submit])', ev.target);
const config_array = inputs.map(u.webForm2xForm).filter(f => f);
try {
await this.model.sendConfiguration(config_array);
} catch (e) {
log.error(e);
const message =
__("Sorry, an error occurred while trying to submit the config form.") + " " +
__("Check your browser's developer console for details.");
api.alert('error', __('Error'), message);
}
await this.model.refreshDiscoInfo();
this.chatroomview.closeForm();
},
closeConfigForm (ev) {
ev.preventDefault();
this.chatroomview.closeForm();
}
});
_converse.MUCPasswordForm = View.extend({
className: 'chatroom-form-container muc-password-form',
initialize (attrs) {
this.chatroomview = attrs.chatroomview;
this.listenTo(this.model, 'change:validation_message', this.render);
this.render();
},
toHTML () {
return tpl_muc_password_form({
'jid': this.model.get('jid'),
'submitPassword': ev => this.submitPassword(ev),
'validation_message': this.model.get('validation_message')
});
},
submitPassword (ev) {
ev.preventDefault();
const password = this.el.querySelector('input[type=password]').value;
this.chatroomview.model.join(this.chatroomview.model.get('nick'), password);
this.model.set('validation_message', null);
}
});
function setMUCDomain (domain, controlboxview) {
controlboxview.getRoomsPanel().model.save('muc_domain', Strophe.getDomainFromJid(domain));
}
function setMUCDomainFromDisco (controlboxview) {
/* Check whether service discovery for the user's domain
* returned MUC information and use that to automatically
* set the MUC domain in the "Add groupchat" modal.
*/
function featureAdded (feature) {
if (!feature) { return; }
if (feature.get('var') === Strophe.NS.MUC) {
feature.entity.getIdentity('conference', 'text').then(identity => {
if (identity) {
setMUCDomain(feature.get('from'), controlboxview);
}
});
}
}
api.waitUntil('discoInitialized').then(() => {
api.listen.on('serviceDiscovered', featureAdded);
// Features could have been added before the controlbox was
// initialized. We're only interested in MUC
_converse.disco_entities.each(entity => featureAdded(entity.features.findWhere({'var': Strophe.NS.MUC })));
}).catch(e => log.error(e));
}
function fetchAndSetMUCDomain (controlboxview) {
if (controlboxview.model.get('connected')) {
if (!controlboxview.getRoomsPanel().model.get('muc_domain')) {
if (api.settings.get('muc_domain') === undefined) {
setMUCDomainFromDisco(controlboxview);
} else {
setMUCDomain(api.settings.get('muc_domain'), controlboxview);
}
}
}
}
/************************ BEGIN Event Handlers ************************/
api.listen.on('chatBoxViewsInitialized', () => {
function openChatRoomFromURIClicked (ev) {
ev.preventDefault();
api.rooms.open(ev.target.href);
}
_converse.chatboxviews.delegate('click', 'a.open-chatroom', openChatRoomFromURIClicked);
async function addView (model) { export default ChatRoomViewMixin;
const views = _converse.chatboxviews;
if (!views.get(model.get('id')) &&
model.get('type') === _converse.CHATROOMS_TYPE &&
model.isValid()
) {
await model.initialized;
return views.add(model.get('id'), new _converse.ChatRoomView({model}));
}
}
_converse.chatboxes.on('add', addView);
});
api.listen.on('clearSession', () => {
const view = _converse.chatboxviews.get('controlbox');
if (view && view.roomspanel) {
view.roomspanel.model.destroy();
view.roomspanel.remove();
delete view.roomspanel;
}
});
api.listen.on('controlBoxInitialized', (view) => {
if (!api.settings.get('allow_muc')) {
return;
}
fetchAndSetMUCDomain(view);
view.model.on('change:connected', () => fetchAndSetMUCDomain(view));
});
/************************ END Event Handlers ************************/
/************************ BEGIN API ************************/
Object.assign(_converse.api, {
/**
* The "roomviews" namespace groups methods relevant to chatroom
* (aka groupchats) views.
*
* @namespace _converse.api.roomviews
* @memberOf _converse.api
*/
roomviews: {
/**
* Retrieves a groupchat (aka chatroom) view. The chat should already be open.
*
* @method _converse.api.roomviews.get
* @param {String|string[]} name - e.g. 'coven@conference.shakespeare.lit' or
* ['coven@conference.shakespeare.lit', 'cave@conference.shakespeare.lit']
* @returns {View} View representing the groupchat
*
* @example
* // To return a single view, provide the JID of the groupchat
* const view = _converse.api.roomviews.get('coven@conference.shakespeare.lit');
*
* @example
* // To return an array of views, provide an array of JIDs:
* const views = _converse.api.roomviews.get(['coven@conference.shakespeare.lit', 'cave@conference.shakespeare.lit']);
*
* @example
* // To return views of all open groupchats, call the method without any parameters::
* const views = _converse.api.roomviews.get();
*
*/
get (jids) {
if (Array.isArray(jids)) {
const views = api.chatviews.get(jids);
return views.filter(v => v.model.get('type') === _converse.CHATROOMS_TYPE)
} else {
const view = api.chatviews.get(jids);
if (view.model.get('type') === _converse.CHATROOMS_TYPE) {
return view;
} else {
return null;
}
}
},
/**
* Lets you close open chatrooms.
*
* You can call this method without any arguments to close
* all open chatrooms, or you can specify a single JID or
* an array of JIDs.
*
* @method _converse.api.roomviews.close
* @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s)
* @returns { Promise } - Promise which resolves once the views have been closed.
*/
close (jids) {
let views;
if (jids === undefined) {
views = _converse.chatboxviews;
} else if (typeof jids === 'string') {
views = [_converse.chatboxviews.get(jids)].filter(v => v);
} else if (Array.isArray(jids)) {
views = jids.map(jid => _converse.chatboxviews.get(jid));
}
return Promise.all(views.map(v => (v.is_chatroom && v.model && v.close())))
}
}
});
}
});
import tpl_muc_password_form from "templates/muc_password_form.js";
import { View } from '@converse/skeletor/src/view.js';
const MUCPasswordForm = View.extend({
className: 'chatroom-form-container muc-password-form',
initialize (attrs) {
this.chatroomview = attrs.chatroomview;
this.listenTo(this.model, 'change:validation_message', this.render);
this.render();
},
toHTML () {
return tpl_muc_password_form({
'jid': this.model.get('jid'),
'submitPassword': ev => this.submitPassword(ev),
'validation_message': this.model.get('validation_message')
});
},
submitPassword (ev) {
ev.preventDefault();
const password = this.el.querySelector('input[type=password]').value;
this.chatroomview.model.join(this.chatroomview.model.get('nick'), password);
this.model.set('validation_message', null);
}
});
export default MUCPasswordForm;
import AddMUCModal from 'modals/add-muc.js';
import tpl_room_panel from 'templates/room_panel.js';
import { View } from '@converse/skeletor/src/view.js';
import MUCListModal from 'modals/muc-list.js';
import { _converse, api, converse } from '@converse/headless/core';
import { __ } from 'i18n';
const u = converse.env.utils;
/**
* View which renders MUC section of the control box.
* @class
* @namespace _converse.RoomsPanel
* @memberOf _converse
*/
export const RoomsPanel = View.extend({
tagName: 'div',
className: 'controlbox-section',
id: 'chatrooms',
events: {
'click a.controlbox-heading__btn.show-add-muc-modal': 'showAddRoomModal',
'click a.controlbox-heading__btn.show-list-muc-modal': 'showMUCListModal'
},
toHTML () {
return tpl_room_panel({
'heading_chatrooms': __('Groupchats'),
'title_new_room': __('Add a new groupchat'),
'title_list_rooms': __('Query for groupchats')
});
},
showAddRoomModal (ev) {
api.modal.show(AddMUCModal, { 'model': this.model }, ev);
},
showMUCListModal (ev) {
api.modal.show(MUCListModal, { 'model': this.model }, ev);
}
});
/**
* Mixin which adds the ability to a ControlBox to render a list of open groupchats
* @mixin
*/
export const RoomsPanelViewMixin = {
renderRoomsPanel () {
if (this.roomspanel && u.isInDOM(this.roomspanel.el)) {
return this.roomspanel;
}
const id = `converse.roomspanel${_converse.bare_jid}`;
this.roomspanel = new _converse.RoomsPanel({
'model': new (_converse.RoomsPanelModel.extend({
id,
'browserStorage': _converse.createStore(id)
}))()
});
this.roomspanel.model.fetch();
this.el.querySelector('.controlbox-pane').insertAdjacentElement('beforeEnd', this.roomspanel.render().el);
/**
* Triggered once the section of the { @link _converse.ControlBoxView }
* which shows gropuchats has been rendered.
* @event _converse#roomsPanelRendered
* @example _converse.api.listen.on('roomsPanelRendered', () => { ... });
*/
api.trigger('roomsPanelRendered');
return this.roomspanel;
},
getRoomsPanel () {
if (this.roomspanel && u.isInDOM(this.roomspanel.el)) {
return this.roomspanel;
} else {
return this.renderRoomsPanel();
}
}
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment