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
import "./plugins/fullscreen.js";
import "./plugins/mam-views.js";
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/notifications.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 ************************/
}
});
/**
* @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 AddMUCModal from '../modals/add-muc.js';
import MUCInviteModal from '../modals/muc-invite.js';
import MUCListModal from '../modals/muc-list.js';
import ModeratorToolsModal from "../modals/moderator-tools.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 './config-form.js';
import './password-form.js';
import MUCInviteModal from 'modals/muc-invite.js';
import ModeratorToolsModal from 'modals/moderator-tools.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_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_spinner from 'templates/spinner.js';
import { Model } from '@converse/skeletor/src/model.js';
import { View } from '@converse/skeletor/src/view.js';
import { __ } from '../i18n';
import { _converse, api, converse } from "@converse/headless/core";
import { debounce } from "lodash-es";
import { render } from "lit-html";
import { __ } from 'i18n';
import { _converse, api, converse } from '@converse/headless/core';
import { debounce } from 'lodash-es';
import { render } from 'lit-html';
const { Strophe, sizzle, $pres } = converse.env;
const u = converse.env.utils;
......@@ -46,24 +32,22 @@ const COMMAND_TO_ROLE = {
'mute': 'visitor',
'op': 'moderator',
'voice': 'participant'
}
};
const COMMAND_TO_AFFILIATION = {
'admin': 'admin',
'ban': 'outcast',
'member': 'member',
'owner': 'owner',
'revoke': 'none'
}
};
/**
* NativeView which renders a groupchat, based upon
* { @link _converse.ChatBoxView } for normal one-on-one chat boxes.
* @class
* Mixin which turns a ChatBoxView into a ChatRoomView
* @mixin
* @namespace _converse.ChatRoomView
* @memberOf _converse
*/
export const ChatRoomView = _converse.ChatBoxView.extend({
const ChatRoomViewMixin = {
length: 300,
tagName: 'div',
className: 'chatbox chatroom hidden',
......@@ -73,7 +57,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'click .hide-occupants': 'hideOccupants',
'click .new-msgs-indicator': 'viewUnreadMessages',
// Arrow functions don't work here because you can't bind a different `this` param to them.
'click .occupant-nick': function (ev) {this.insertIntoTextArea(ev.target.textContent) },
'click .occupant-nick': function (ev) {
this.insertIntoTextArea(ev.target.textContent);
},
'click .send-button': 'onFormSubmitted',
'dragover .chat-textarea': 'onDragOver',
'drop .chat-textarea': 'onDrop',
......@@ -82,15 +68,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'keyup .chat-textarea': 'onKeyUp',
'mousedown .dragresize-occupants-left': 'onStartResizeOccupants',
'paste .chat-textarea': 'onPaste',
'submit .muc-nickname-form': 'submitNickname',
'submit .muc-nickname-form': 'submitNickname'
},
async initialize () {
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: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, 'configurationNeeded', this.getAndRenderConfigurationForm);
this.listenTo(this.model, 'destroy', this.hide);
......@@ -101,8 +91,8 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
this.listenTo(this.model.session, 'change:connection_status', this.onConnectionStatusChanged);
// Bind so that we can pass it to addEventListener and removeEventListener
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
await this.render();
......@@ -142,16 +132,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
async render () {
const sidebar_hidden = !this.shouldShowSidebar();
this.el.setAttribute('id', this.model.get('box_id'));
render(tpl_chatroom({
sidebar_hidden,
'model': this.model,
'occupants': this.model.occupants,
'show_sidebar': !this.model.get('hidden_occupants') &&
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED,
'markScrolled': ev => this.markScrolled(ev),
'muc_show_logs_before_join': api.settings.get('muc_show_logs_before_join'),
'show_send_button': _converse.show_send_button,
}), this.el);
render(
tpl_chatroom({
sidebar_hidden,
'model': this.model,
'occupants': this.model.occupants,
'show_sidebar':
!this.model.get('hidden_occupants') &&
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED,
'markScrolled': ev => this.markScrolled(ev),
'muc_show_logs_before_join': api.settings.get('muc_show_logs_before_join'),
'show_send_button': _converse.show_send_button
}),
this.el
);
this.notifications = this.el.querySelector('.chat-content__notifications');
this.content = this.el.querySelector('.chat-content');
......@@ -159,8 +153,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
this.help_container = this.el.querySelector('.chat-content__help');
this.renderBottomPanel();
if (!api.settings.get('muc_show_logs_before_join') &&
this.model.session.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
if (
!api.settings.get('muc_show_logs_before_join') &&
this.model.session.get('connection_status') !== converse.ROOMSTATUS.ENTERED
) {
this.showSpinner();
}
// Render header as late as possible since it's async and we
......@@ -173,17 +169,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
getNotifications () {
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));
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));
const states = [...converse.CHAT_STATES, ...join_leave_events, ...role_changes];
return states.reduce((result, state) => {
const existing_actors = actors_per_state[state];
if (!(existing_actors?.length)) {
if (!existing_actors?.length) {
return result;
}
const actors = existing_actors.map(a => this.model.getOccupant(a)?.getDisplayName() || a);
......@@ -199,18 +197,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} else if (state === 'exited') {
return `${result}${__('%1$s has left the groupchat', actors[0])}\n`;
} 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') {
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') {
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') {
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) {
let actors_str;
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 {
const last_actor = actors.pop();
actors_str = __('%1$s and %2$s', actors.join(', '), last_actor);
......@@ -227,13 +227,13 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} else if (state === 'exited') {
return `${result}${__('%1$s have left the groupchat', actors_str)}\n`;
} 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') {
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') {
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') {
return `${result}${__("%1$s have been muted", actors[0])}\n`;
return `${result}${__('%1$s have been muted', actors[0])}\n`;
}
}
return result;
......@@ -241,7 +241,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
},
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 : [];
return [
`<strong>/admin</strong>: ${__("Change user's affiliation to admin")}`,
......@@ -259,13 +259,14 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
`<strong>/nick</strong>: ${__('Change your nickname')}`,
`<strong>/op</strong>: ${__('Grant moderator role to user')}`,
`<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>/subject</strong>: ${__('Set groupchat subject')}`,
`<strong>/topic</strong>: ${__('Set groupchat subject (alias for /subject)')}`,
`<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({
const sidebar_el = this.el.querySelector('converse-muc-sidebar');
const element_position = sidebar_el.getBoundingClientRect();
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);
}
},
......@@ -333,23 +334,23 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
this.is_maximum = element_position.left > current_mouse_position;
} else {
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;
const room_width = this.el.clientWidth;
// keeping display in boundaries
if (occupants_width < (room_width * 0.20)) {
if (occupants_width < room_width * 0.2) {
// set pixel to 20% width
occupants_width = (room_width * 0.20);
occupants_width = room_width * 0.2;
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
occupants_width = (room_width * 0.75);
occupants_width = room_width * 0.75;
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)
occupants_width = room_width - 250;
this.is_maximum = true;
......@@ -361,39 +362,39 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
},
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();
const element = document.createElement("li");
element.setAttribute("aria-selected", "false");
const element = document.createElement('li');
element.setAttribute('aria-selected', 'false');
if (api.settings.get('muc_mention_autocomplete_show_avatar')) {
const img = document.createElement("img");
let dataUri = "data:" + _converse.DEFAULT_IMAGE_TYPE + ";base64," + _converse.DEFAULT_IMAGE;
const img = document.createElement('img');
let dataUri = 'data:' + _converse.DEFAULT_IMAGE_TYPE + ';base64,' + _converse.DEFAULT_IMAGE;
if (_converse.vcards) {
const vcard = _converse.vcards.findWhere({'nickname': text});
if (vcard) dataUri = "data:" + vcard.get('image_type') + ";base64," + vcard.get('image');
const vcard = _converse.vcards.findWhere({ 'nickname': text });
if (vcard) dataUri = 'data:' + vcard.get('image_type') + ';base64,' + vcard.get('image');
}
img.setAttribute("src", dataUri);
img.setAttribute("width", "22");
img.setAttribute("class", "avatar avatar-autocomplete");
img.setAttribute('src', dataUri);
img.setAttribute('width', '22');
img.setAttribute('class', 'avatar avatar-autocomplete');
element.appendChild(img);
}
const regex = new RegExp("(" + input + ")", "ig");
const regex = new RegExp('(' + input + ')', 'ig');
const parts = input ? text.split(regex) : [text];
parts.forEach(txt => {
if (input && txt.match(regex)) {
const match = document.createElement("mark");
match.textContent = txt;
element.appendChild(match);
const match = document.createElement('mark');
match.textContent = txt;
element.appendChild(match);
} else {
element.appendChild(document.createTextNode(txt));
element.appendChild(document.createTextNode(txt));
}
});
......@@ -407,10 +408,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'min_chars': api.settings.get('muc_mention_autocomplete_min_chars'),
'match_current_word': true,
'list': () => this.getAutoCompleteList(),
'filter': api.settings.get('muc_mention_autocomplete_filter') == 'contains' ?
_converse.FILTER_CONTAINS :
_converse.FILTER_STARTSWITH,
'ac_triggers': ["Tab", "@"],
'filter':
api.settings.get('muc_mention_autocomplete_filter') == 'contains'
? _converse.FILTER_CONTAINS
: _converse.FILTER_STARTSWITH,
'ac_triggers': ['Tab', '@'],
'include_triggers': [],
'item': this.getAutoCompleteListItem
});
......@@ -442,10 +444,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
},
async onMessageRetractButtonClicked (message) {
const retraction_warning =
__("Be aware that other XMPP/Jabber clients (and servers) may "+
"not yet support retractions and that this message may not "+
"be removed everywhere.");
const retraction_warning = __(
'Be aware that other XMPP/Jabber clients (and servers) may ' +
'not yet support retractions and that this message may not ' +
'be removed everywhere.'
);
if (message.mayBeRetracted()) {
const messages = [__('Are you sure you want to retract this message?')];
......@@ -457,7 +460,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (message.get('sender') === 'me') {
let messages = [__('Are you sure you want to retract this message?')];
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);
} else {
......@@ -466,14 +469,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
__('You may optionally include a message, explaining the reason for the retraction.')
];
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(
__('Message Retraction'),
messages,
__('Optional reason')
);
(reason !== false) && this.retractOtherMessage(message, reason);
const reason = await api.prompt(__('Message Retraction'), messages, __('Optional reason'));
reason !== false && this.retractOtherMessage(message, reason);
}
} else {
const err_msg = __(`Sorry, you're not allowed to retract this message`);
......@@ -510,20 +509,20 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (modal) {
modal.model.set('affiliation', affiliation);
} else {
const model = new Model({'affiliation': affiliation});
modal = api.modal.create(ModeratorToolsModal, {model, _converse, 'chatroomview': this});
const model = new Model({ 'affiliation': affiliation });
modal = api.modal.create(ModeratorToolsModal, { model, _converse, 'chatroomview': this });
}
modal.show();
},
showRoomDetailsModal (ev) {
ev.preventDefault();
api.modal.show(RoomDetailsModal, {'model': this.model}, ev);
api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
},
showOccupantDetailsModal (ev, message) {
ev.preventDefault();
api.modal.show(OccupantModal, {'model': message.occupant}, ev);
api.modal.show(OccupantModal, { 'model': message.occupant }, ev);
},
showChatStateNotification (message) {
......@@ -534,8 +533,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
},
shouldShowSidebar () {
return !this.model.get('hidden_occupants') &&
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED;
return (
!this.model.get('hidden_occupants') &&
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED
);
},
onSidebarToggle () {
......@@ -598,9 +599,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
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'),
'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',
......@@ -608,7 +609,6 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
});
}
const conn_status = this.model.session.get('connection_status');
if (conn_status === converse.ROOMSTATUS.ENTERED) {
const allowed_commands = this.getAllowedCommands();
......@@ -634,7 +634,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}
}
if (!api.settings.get("singleton")) {
if (!api.settings.get('singleton')) {
buttons.push({
'i18n_text': __('Leave'),
'i18n_title': __('Leave and close this groupchat'),
......@@ -645,7 +645,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
result && this.close(ev);
},
'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',
'name': 'signout'
});
......@@ -669,8 +669,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
subject_hidden,
'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)),
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b)),
'title': this.model.getDisplayName(),
}));
'title': this.model.getDisplayName()
})
);
},
toggleTopic () {
......@@ -679,10 +680,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
showInviteModal (ev) {
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.
* @private
......@@ -718,13 +718,11 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
},
getToolbarOptions () {
return Object.assign(
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), {
'is_groupchat': true,
'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
}
);
return Object.assign(_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), {
'is_groupchat': true,
'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
});
},
/**
......@@ -734,7 +732,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
*/
async close () {
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('');
}
await this.model.leave();
......@@ -751,18 +749,18 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
ev.preventDefault();
ev.stopPropagation();
}
this.model.save({'hidden_occupants': true});
this.model.save({ 'hidden_occupants': true });
this.scrollDown();
},
verifyRoles (roles, occupant, show_error=true) {
verifyRoles (roles, occupant, show_error = true) {
if (!Array.isArray(roles)) {
throw new TypeError('roles must be an Array');
}
if (!roles.length) {
return true;
}
occupant = occupant || this.model.occupants.findWhere({'jid': _converse.bare_jid});
occupant = occupant || this.model.occupants.findWhere({ 'jid': _converse.bare_jid });
if (occupant) {
const role = occupant.get('role');
if (roles.includes(role)) {
......@@ -771,19 +769,19 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}
if (show_error) {
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;
},
verifyAffiliations (affiliations, occupant, show_error=true) {
verifyAffiliations (affiliations, occupant, show_error = true) {
if (!Array.isArray(affiliations)) {
throw new TypeError('affiliations must be an Array');
}
if (!affiliations.length) {
return true;
}
occupant = occupant || this.model.occupants.findWhere({'jid': _converse.bare_jid});
occupant = occupant || this.model.occupants.findWhere({ 'jid': _converse.bare_jid });
if (occupant) {
const a = occupant.get('affiliation');
if (affiliations.includes(a)) {
......@@ -792,7 +790,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}
if (show_error) {
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;
},
......@@ -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.',
command
);
this.model.createMessage({message, 'type': 'error'});
this.model.createMessage({ message, 'type': 'error' });
return false;
}
return true;
......@@ -814,24 +812,24 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
return args.trim();
}
if (!args.startsWith('@')) {
args = '@'+ args;
args = '@' + args;
}
const [text, references] = this.model.parseTextForReferences(args); // eslint-disable-line no-unused-vars
if (!references.length) {
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;
}
if (references.length > 1) {
const message = __("Error: found multiple groupchat participant based on your arguments");
this.model.createMessage({message, 'type': 'error'});
const message = __('Error: found multiple groupchat participant based on your arguments');
this.model.createMessage({ message, 'type': 'error' });
return;
}
const nick_or_jid = references.pop().value;
const reason = args.split(nick_or_jid, 2)[1];
if (reason && !reason.startsWith(' ')) {
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 nick_or_jid;
......@@ -863,10 +861,9 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
jid = nick_or_jid;
} else {
const message = __(
"Couldn't find a participant with that nickname. "+
"They might have left the groupchat."
"Couldn't find a participant with that nickname. " + 'They might have left the groupchat.'
);
this.model.createMessage({message, 'type': 'error'});
this.model.createMessage({ message, 'type': 'error' });
return;
}
}
......@@ -874,16 +871,17 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (occupant && api.settings.get('auto_register_muc_nickname')) {
attrs['nick'] = occupant.get('nick');
}
this.model.setAffiliation(affiliation, [attrs])
this.model
.setAffiliation(affiliation, [attrs])
.then(() => this.model.occupants.fetchMembers())
.catch(err => this.onCommandError(err));
},
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
* affiliation has anough arguments.
*/
......@@ -911,9 +909,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
onCommandError (err) {
log.fatal(err);
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.");
this.model.createMessage({message, 'type': 'error'});
this.model.createMessage({ message, 'type': 'error' });
},
getAllowedCommands () {
......@@ -921,7 +920,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
if (this.model.config.get('changesubject') || ['owner', 'admin'].includes(this.model.getOwnAffiliation())) {
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)) {
allowed_commands = allowed_commands.concat(OWNER_COMMANDS).concat(ADMIN_COMMANDS);
} else if (this.verifyAffiliations(['admin'], occupant, false)) {
......@@ -943,42 +942,48 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
async destroy () {
const messages = [__('Are you sure you want to destroy this groupchat?')];
let fields = [{
'name': 'challenge',
'label': __('Please enter the XMPP address of this groupchat to confirm'),
'challenge': this.model.get('jid'),
'placeholder': __('name@example.org'),
'required': true
}, {
'name': 'reason',
'label': __('Optional reason for destroying this groupchat'),
'placeholder': __('Reason')
}, {
'name': 'newjid',
'label': __('Optional XMPP address for a new groupchat that replaces this one'),
'placeholder': __('replacement@example.org')
}];
let fields = [
{
'name': 'challenge',
'label': __('Please enter the XMPP address of this groupchat to confirm'),
'challenge': this.model.get('jid'),
'placeholder': __('name@example.org'),
'required': true
},
{
'name': 'reason',
'label': __('Optional reason for destroying this groupchat'),
'placeholder': __('Reason')
},
{
'name': 'newjid',
'label': __('Optional XMPP address for a new groupchat that replaces this one'),
'placeholder': __('replacement@example.org')
}
];
try {
fields = await api.confirm(__('Confirm'), messages, fields);
const reason = fields.filter(f => f.name === 'reason').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) {
log.error(e);
}
},
parseMessageForCommands (text) {
if (api.settings.get('muc_disable_slash_commands') &&
!Array.isArray(api.settings.get('muc_disable_slash_commands'))) {
if (
api.settings.get('muc_disable_slash_commands') &&
!Array.isArray(api.settings.get('muc_disable_slash_commands'))
) {
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();
if (!command) {
return false;
}
const args = text.slice(('/'+command).length+1).trim();
const args = text.slice(('/' + command).length + 1).trim();
if (!this.getAllowedCommands().includes(command)) {
return false;
}
......@@ -1014,9 +1019,10 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
break;
}
case 'help': {
this.model.set({'show_help_messages': true});
this.model.set({ 'show_help_messages': true });
break;
} case 'kick': {
}
case 'kick': {
this.setRole(command, args, [], ['moderator']);
break;
}
......@@ -1034,15 +1040,16 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
} else if (args.length === 0) {
// e.g. Your nickname is "coolguy69"
const message = __('Your nickname is "%1$s"', this.model.get('nick'));
this.model.createMessage({message, 'type': 'error'});
this.model.createMessage({ message, 'type': 'error' });
} else {
const jid = Strophe.getBareJidFromJid(this.model.get('jid'));
api.send($pres({
from: _converse.connection.jid,
to: `${jid}/${args}`,
id: u.getUniqueId()
}).tree());
api.send(
$pres({
from: _converse.connection.jid,
to: `${jid}/${args}`,
id: u.getUniqueId()
}).tree()
);
}
break;
}
......@@ -1061,7 +1068,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
});
} else {
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;
......@@ -1130,7 +1137,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
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({
getAndRenderConfigurationForm () {
if (!this.config_form || !u.isVisible(this.config_form.el)) {
this.showSpinner();
this.model.fetchRoomConfiguration()
this.model
.fetchRoomConfiguration()
.then(iq => this.renderConfigurationForm(iq))
.catch(e => log.error(e));
} else {
......@@ -1185,7 +1193,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
'model': new Model({
'validation_message': message
}),
'chatroomview': this,
'chatroomview': this
});
const container_el = this.el.querySelector('.chatroom-body');
container_el.insertAdjacentElement('beforeend', this.password_form.el);
......@@ -1265,7 +1273,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
* @param {string} nick
*/
getPreviousJoinOrLeaveNotification (el, nick) {
const today = (new Date()).toISOString().split('T')[0];
const today = new Date().toISOString().split('T')[0];
while (el !== null) {
if (!el.classList.contains('chat-info')) {
return;
......@@ -1277,10 +1285,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
return;
}
const data = el?.dataset || {};
if (data.join === nick ||
data.leave === nick ||
data.leavejoin === nick ||
data.joinleave === nick) {
if (data.join === nick || data.leave === nick || data.leavejoin === nick || data.joinleave === nick) {
return el;
}
el = el.previousElementSibling;
......@@ -1295,7 +1300,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
* @method _converse.ChatRoomView#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) {
this.renderNicknameForm();
} else if (conn_status == converse.ROOMSTATUS.PASSWORD_REQUIRED) {
......@@ -1312,10 +1317,7 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
sizzle('.spinner', this.el).forEach(u.removeElement);
this.hideChatRoomContents();
const container_el = this.el.querySelector('.chatroom-body');
container_el.insertAdjacentElement(
'afterbegin',
u.getElementFromTemplateResult(tpl_spinner())
);
container_el.insertAdjacentElement('afterbegin', u.getElementFromTemplateResult(tpl_spinner()));
},
/**
......@@ -1333,371 +1335,6 @@ export const ChatRoomView = _converse.ChatBoxView.extend({
}
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) {
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())))
}
}
});
}
});
export default ChatRoomViewMixin;
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