2020-12-06 20:42:12 +01:00
|
|
|
import './config-form.js';
|
|
|
|
import './password-form.js';
|
2020-12-10 11:08:17 +01:00
|
|
|
import 'shared/autocomplete/index.js';
|
2020-12-06 20:42:12 +01:00
|
|
|
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';
|
2020-12-08 14:51:04 +01:00
|
|
|
import { $build, $pres, Strophe } from 'strophe.js/src/strophe';
|
2020-12-06 20:42:12 +01:00
|
|
|
import tpl_muc_nickname_form from 'templates/muc_nickname_form.js';
|
|
|
|
import tpl_spinner from 'templates/spinner.js';
|
2020-06-08 16:08:50 +02:00
|
|
|
import { Model } from '@converse/skeletor/src/model.js';
|
2020-12-06 20:42:12 +01:00
|
|
|
import { __ } from 'i18n';
|
|
|
|
import { _converse, api, converse } from '@converse/headless/core';
|
|
|
|
import { debounce } from 'lodash-es';
|
|
|
|
import { render } from 'lit-html';
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-12-08 14:51:04 +01:00
|
|
|
const { sizzle } = converse.env;
|
2018-10-23 03:41:38 +02:00
|
|
|
const u = converse.env.utils;
|
2019-07-04 14:12:12 +02:00
|
|
|
|
2019-04-25 10:34:17 +02:00
|
|
|
const OWNER_COMMANDS = ['owner'];
|
2019-09-07 23:41:48 +02:00
|
|
|
const ADMIN_COMMANDS = ['admin', 'ban', 'deop', 'destroy', 'member', 'op', 'revoke'];
|
|
|
|
const MODERATOR_COMMANDS = ['kick', 'mute', 'voice', 'modtools'];
|
2019-04-25 10:34:17 +02:00
|
|
|
const VISITOR_COMMANDS = ['nick'];
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2019-05-26 17:53:32 +02:00
|
|
|
const COMMAND_TO_ROLE = {
|
|
|
|
'deop': 'participant',
|
|
|
|
'kick': 'none',
|
|
|
|
'mute': 'visitor',
|
|
|
|
'op': 'moderator',
|
|
|
|
'voice': 'participant'
|
2020-12-06 20:42:12 +01:00
|
|
|
};
|
2019-05-26 17:53:32 +02:00
|
|
|
const COMMAND_TO_AFFILIATION = {
|
|
|
|
'admin': 'admin',
|
|
|
|
'ban': 'outcast',
|
|
|
|
'member': 'member',
|
|
|
|
'owner': 'owner',
|
|
|
|
'revoke': 'none'
|
2020-12-06 20:42:12 +01:00
|
|
|
};
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
2020-12-06 20:42:12 +01:00
|
|
|
* Mixin which turns a ChatBoxView into a ChatRoomView
|
|
|
|
* @mixin
|
2020-09-09 14:37:43 +02:00
|
|
|
* @namespace _converse.ChatRoomView
|
|
|
|
* @memberOf _converse
|
|
|
|
*/
|
2020-12-06 20:42:12 +01:00
|
|
|
const ChatRoomViewMixin = {
|
2020-09-09 14:37:43 +02:00
|
|
|
length: 300,
|
|
|
|
tagName: 'div',
|
|
|
|
className: 'chatbox chatroom hidden',
|
|
|
|
is_chatroom: true,
|
|
|
|
events: {
|
|
|
|
'click .chatbox-navback': 'showControlBox',
|
|
|
|
'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.
|
2020-12-06 20:42:12 +01:00
|
|
|
'click .occupant-nick': function (ev) {
|
|
|
|
this.insertIntoTextArea(ev.target.textContent);
|
|
|
|
},
|
2020-09-09 14:37:43 +02:00
|
|
|
'click .send-button': 'onFormSubmitted',
|
|
|
|
'dragover .chat-textarea': 'onDragOver',
|
|
|
|
'drop .chat-textarea': 'onDrop',
|
|
|
|
'input .chat-textarea': 'inputChanged',
|
|
|
|
'keydown .chat-textarea': 'onKeyDown',
|
|
|
|
'keyup .chat-textarea': 'onKeyUp',
|
|
|
|
'mousedown .dragresize-occupants-left': 'onStartResizeOccupants',
|
|
|
|
'paste .chat-textarea': 'onPaste',
|
2020-12-06 20:42:12 +01:00
|
|
|
'submit .muc-nickname-form': 'submitNickname'
|
2018-10-23 03:41:38 +02:00
|
|
|
},
|
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
async initialize () {
|
|
|
|
this.initDebounced();
|
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
this.listenTo(
|
|
|
|
this.model,
|
|
|
|
'change',
|
|
|
|
debounce(() => this.renderHeading(), 250)
|
|
|
|
);
|
2020-09-09 14:37:43 +02:00
|
|
|
this.listenTo(this.model, 'change:composing_spoiler', this.renderMessageForm);
|
2020-12-06 20:42:12 +01:00
|
|
|
this.listenTo(this.model, 'change:hidden', m => (m.get('hidden') ? this.hide() : this.show()));
|
2020-10-23 20:49:10 +02:00
|
|
|
this.listenTo(this.model, 'change:hidden_occupants', this.onSidebarToggle);
|
2020-09-09 14:37:43 +02:00
|
|
|
this.listenTo(this.model, 'configurationNeeded', this.getAndRenderConfigurationForm);
|
|
|
|
this.listenTo(this.model, 'destroy', this.hide);
|
|
|
|
this.listenTo(this.model, 'show', this.show);
|
|
|
|
this.listenTo(this.model.features, 'change:moderated', this.renderBottomPanel);
|
|
|
|
this.listenTo(this.model.features, 'change:open', this.renderHeading);
|
|
|
|
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown);
|
|
|
|
this.listenTo(this.model.session, 'change:connection_status', this.onConnectionStatusChanged);
|
|
|
|
|
|
|
|
// Bind so that we can pass it to addEventListener and removeEventListener
|
2020-12-06 20:42:12 +01:00
|
|
|
this.onMouseMove = this.onMouseMove.bind(this);
|
|
|
|
this.onMouseUp = this.onMouseUp.bind(this);
|
2020-09-09 14:37:43 +02:00
|
|
|
|
|
|
|
await this.render();
|
|
|
|
|
|
|
|
// Need to be registered after render has been called.
|
|
|
|
this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages);
|
|
|
|
this.listenTo(this.model.messages, 'add', this.onMessageAdded);
|
|
|
|
this.listenTo(this.model.messages, 'change', this.renderChatHistory);
|
|
|
|
this.listenTo(this.model.messages, 'remove', this.renderChatHistory);
|
|
|
|
this.listenTo(this.model.messages, 'reset', this.renderChatHistory);
|
|
|
|
this.listenTo(this.model.notifications, 'change', this.renderNotifications);
|
|
|
|
|
|
|
|
this.model.occupants.forEach(o => this.onOccupantAdded(o));
|
|
|
|
this.listenTo(this.model.occupants, 'add', this.onOccupantAdded);
|
|
|
|
this.listenTo(this.model.occupants, 'change', this.renderChatHistory);
|
|
|
|
this.listenTo(this.model.occupants, 'change:affiliation', this.onOccupantAffiliationChanged);
|
|
|
|
this.listenTo(this.model.occupants, 'change:role', this.onOccupantRoleChanged);
|
|
|
|
this.listenTo(this.model.occupants, 'change:show', this.showJoinOrLeaveNotification);
|
|
|
|
this.listenTo(this.model.occupants, 'remove', this.onOccupantRemoved);
|
|
|
|
|
2020-10-28 13:02:12 +01:00
|
|
|
this.renderChatContent();
|
|
|
|
this.insertIntoDOM();
|
2020-09-09 14:37:43 +02:00
|
|
|
// Register later due to await
|
|
|
|
const user_settings = await _converse.api.user.settings.getModel();
|
|
|
|
this.listenTo(user_settings, 'change:mucs_with_hidden_subject', this.renderHeading);
|
|
|
|
this.onConnectionStatusChanged();
|
|
|
|
this.model.maybeShow();
|
2020-10-28 13:02:12 +01:00
|
|
|
this.scrollDown();
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Triggered once a { @link _converse.ChatRoomView } has been opened
|
|
|
|
* @event _converse#chatRoomViewInitialized
|
|
|
|
* @type { _converse.ChatRoomView }
|
|
|
|
* @example _converse.api.listen.on('chatRoomViewInitialized', view => { ... });
|
|
|
|
*/
|
|
|
|
api.trigger('chatRoomViewInitialized', this);
|
|
|
|
},
|
2019-10-11 20:29:12 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
async render () {
|
2020-10-23 20:49:10 +02:00
|
|
|
const sidebar_hidden = !this.shouldShowSidebar();
|
2020-09-09 14:37:43 +02:00
|
|
|
this.el.setAttribute('id', this.model.get('box_id'));
|
2020-12-06 20:42:12 +01:00
|
|
|
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
|
|
|
|
);
|
2020-09-09 14:37:43 +02:00
|
|
|
|
|
|
|
this.notifications = this.el.querySelector('.chat-content__notifications');
|
|
|
|
this.content = this.el.querySelector('.chat-content');
|
|
|
|
this.msgs_container = this.el.querySelector('.chat-content__messages');
|
|
|
|
this.help_container = this.el.querySelector('.chat-content__help');
|
|
|
|
|
|
|
|
this.renderBottomPanel();
|
2020-12-06 20:42:12 +01:00
|
|
|
if (
|
|
|
|
!api.settings.get('muc_show_logs_before_join') &&
|
|
|
|
this.model.session.get('connection_status') !== converse.ROOMSTATUS.ENTERED
|
|
|
|
) {
|
2020-09-09 14:37:43 +02:00
|
|
|
this.showSpinner();
|
|
|
|
}
|
|
|
|
// Render header as late as possible since it's async and we
|
|
|
|
// want the rest of the DOM elements to be available ASAP.
|
|
|
|
// Otherwise e.g. this.notifications is not yet defined when accessed elsewhere.
|
|
|
|
await this.renderHeading();
|
|
|
|
!this.model.get('hidden') && this.show();
|
|
|
|
},
|
2019-05-24 20:50:30 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
getNotifications () {
|
|
|
|
const actors_per_state = this.model.notifications.toJSON();
|
2020-12-02 14:53:22 +01:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
const role_changes = api.settings
|
|
|
|
.get('muc_show_info_messages')
|
2020-12-02 14:53:22 +01:00
|
|
|
.filter(role_change => converse.MUC_ROLE_CHANGES_LIST.includes(role_change));
|
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
const join_leave_events = api.settings
|
|
|
|
.get('muc_show_info_messages')
|
2020-12-02 14:53:22 +01:00
|
|
|
.filter(join_leave_event => converse.MUC_TRAFFIC_STATES_LIST.includes(join_leave_event));
|
|
|
|
|
|
|
|
const states = [...converse.CHAT_STATES, ...join_leave_events, ...role_changes];
|
2019-09-11 11:28:28 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
return states.reduce((result, state) => {
|
|
|
|
const existing_actors = actors_per_state[state];
|
2020-12-06 20:42:12 +01:00
|
|
|
if (!existing_actors?.length) {
|
2020-09-09 14:37:43 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
const actors = existing_actors.map(a => this.model.getOccupant(a)?.getDisplayName() || a);
|
|
|
|
if (actors.length === 1) {
|
|
|
|
if (state === 'composing') {
|
|
|
|
return `${result}${__('%1$s is typing', actors[0])}\n`;
|
|
|
|
} else if (state === 'paused') {
|
|
|
|
return `${result}${__('%1$s has stopped typing', actors[0])}\n`;
|
|
|
|
} else if (state === _converse.GONE) {
|
|
|
|
return `${result}${__('%1$s has gone away', actors[0])}\n`;
|
|
|
|
} else if (state === 'entered') {
|
|
|
|
return `${result}${__('%1$s has entered the groupchat', actors[0])}\n`;
|
|
|
|
} else if (state === 'exited') {
|
|
|
|
return `${result}${__('%1$s has left the groupchat', actors[0])}\n`;
|
|
|
|
} else if (state === 'op') {
|
2020-12-06 20:42:12 +01:00
|
|
|
return `${result}${__('%1$s is now a moderator', actors[0])}\n`;
|
2020-09-09 14:37:43 +02:00
|
|
|
} else if (state === 'deop') {
|
2020-12-06 20:42:12 +01:00
|
|
|
return `${result}${__('%1$s is no longer a moderator', actors[0])}\n`;
|
2020-09-09 14:37:43 +02:00
|
|
|
} else if (state === 'voice') {
|
2020-12-06 20:42:12 +01:00
|
|
|
return `${result}${__('%1$s has been given a voice', actors[0])}\n`;
|
2020-09-09 14:37:43 +02:00
|
|
|
} else if (state === 'mute') {
|
2020-12-06 20:42:12 +01:00
|
|
|
return `${result}${__('%1$s has been muted', actors[0])}\n`;
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
} else if (actors.length > 1) {
|
|
|
|
let actors_str;
|
|
|
|
if (actors.length > 3) {
|
2020-12-06 20:42:12 +01:00
|
|
|
actors_str = `${Array.from(actors)
|
|
|
|
.slice(0, 2)
|
|
|
|
.join(', ')} and others`;
|
2019-09-11 11:28:28 +02:00
|
|
|
} else {
|
2020-09-09 14:37:43 +02:00
|
|
|
const last_actor = actors.pop();
|
|
|
|
actors_str = __('%1$s and %2$s', actors.join(', '), last_actor);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state === 'composing') {
|
|
|
|
return `${result}${__('%1$s are typing', actors_str)}\n`;
|
|
|
|
} else if (state === 'paused') {
|
|
|
|
return `${result}${__('%1$s have stopped typing', actors_str)}\n`;
|
|
|
|
} else if (state === _converse.GONE) {
|
|
|
|
return `${result}${__('%1$s have gone away', actors_str)}\n`;
|
|
|
|
} else if (state === 'entered') {
|
|
|
|
return `${result}${__('%1$s have entered the groupchat', actors_str)}\n`;
|
|
|
|
} else if (state === 'exited') {
|
|
|
|
return `${result}${__('%1$s have left the groupchat', actors_str)}\n`;
|
|
|
|
} else if (state === 'op') {
|
2020-12-06 20:42:12 +01:00
|
|
|
return `${result}${__('%1$s are now moderators', actors[0])}\n`;
|
2020-09-09 14:37:43 +02:00
|
|
|
} else if (state === 'deop') {
|
2020-12-06 20:42:12 +01:00
|
|
|
return `${result}${__('%1$s are no longer moderators', actors[0])}\n`;
|
2020-09-09 14:37:43 +02:00
|
|
|
} else if (state === 'voice') {
|
2020-12-06 20:42:12 +01:00
|
|
|
return `${result}${__('%1$s have been given voices', actors[0])}\n`;
|
2020-09-09 14:37:43 +02:00
|
|
|
} else if (state === 'mute') {
|
2020-12-06 20:42:12 +01:00
|
|
|
return `${result}${__('%1$s have been muted', actors[0])}\n`;
|
2019-09-11 11:28:28 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-09 14:37:43 +02:00
|
|
|
return result;
|
|
|
|
}, '');
|
|
|
|
},
|
2019-09-11 11:28:28 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
getHelpMessages () {
|
2020-12-06 20:42:12 +01:00
|
|
|
const setting = api.settings.get('muc_disable_slash_commands');
|
2020-09-09 14:37:43 +02:00
|
|
|
const disabled_commands = Array.isArray(setting) ? setting : [];
|
|
|
|
return [
|
|
|
|
`<strong>/admin</strong>: ${__("Change user's affiliation to admin")}`,
|
|
|
|
`<strong>/ban</strong>: ${__('Ban user by changing their affiliation to outcast')}`,
|
|
|
|
`<strong>/clear</strong>: ${__('Clear the chat area')}`,
|
|
|
|
`<strong>/close</strong>: ${__('Close this groupchat')}`,
|
|
|
|
`<strong>/deop</strong>: ${__('Change user role to participant')}`,
|
|
|
|
`<strong>/destroy</strong>: ${__('Remove this groupchat')}`,
|
|
|
|
`<strong>/help</strong>: ${__('Show this menu')}`,
|
|
|
|
`<strong>/kick</strong>: ${__('Kick user from groupchat')}`,
|
|
|
|
`<strong>/me</strong>: ${__('Write in 3rd person')}`,
|
|
|
|
`<strong>/member</strong>: ${__('Grant membership to a user')}`,
|
|
|
|
`<strong>/modtools</strong>: ${__('Opens up the moderator tools GUI')}`,
|
|
|
|
`<strong>/mute</strong>: ${__("Remove user's ability to post messages")}`,
|
|
|
|
`<strong>/nick</strong>: ${__('Change your nickname')}`,
|
|
|
|
`<strong>/op</strong>: ${__('Grant moderator role to user')}`,
|
|
|
|
`<strong>/owner</strong>: ${__('Grant ownership of this groupchat')}`,
|
2020-12-06 20:42:12 +01:00
|
|
|
`<strong>/register</strong>: ${__('Register your nickname')}`,
|
2020-09-09 14:37:43 +02:00
|
|
|
`<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')}`
|
2020-12-06 20:42:12 +01:00
|
|
|
]
|
|
|
|
.filter(line => disabled_commands.every(c => !line.startsWith(c + '<', 9)))
|
|
|
|
.filter(line => this.getAllowedCommands().some(c => line.startsWith(c + '<', 9)));
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2019-05-24 13:52:15 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Renders the MUC heading if any relevant attributes have changed.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#renderHeading
|
|
|
|
* @param { _converse.ChatRoom } [item]
|
|
|
|
*/
|
|
|
|
async renderHeading () {
|
|
|
|
const tpl = await this.generateHeadingTemplate();
|
|
|
|
render(tpl, this.el.querySelector('.chat-head-chatroom'));
|
|
|
|
},
|
2018-03-26 21:11:32 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
renderBottomPanel () {
|
|
|
|
const container = this.el.querySelector('.bottom-panel');
|
|
|
|
const entered = this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED;
|
|
|
|
const can_edit = entered && !(this.model.features.get('moderated') && this.model.getOwnRole() === 'visitor');
|
2020-09-26 13:13:50 +02:00
|
|
|
render(tpl_muc_bottom_panel({ can_edit, entered }), container);
|
2020-09-09 14:37:43 +02:00
|
|
|
if (entered && can_edit) {
|
|
|
|
this.renderMessageForm();
|
|
|
|
this.initMentionAutoComplete();
|
|
|
|
}
|
|
|
|
},
|
2020-04-10 16:49:02 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onStartResizeOccupants (ev) {
|
|
|
|
this.resizing = true;
|
|
|
|
this.el.addEventListener('mousemove', this.onMouseMove);
|
|
|
|
this.el.addEventListener('mouseup', this.onMouseUp);
|
2018-02-22 18:41:01 +01:00
|
|
|
|
2020-10-23 20:49:10 +02:00
|
|
|
const sidebar_el = this.el.querySelector('converse-muc-sidebar');
|
|
|
|
const style = window.getComputedStyle(sidebar_el);
|
2020-09-09 14:37:43 +02:00
|
|
|
this.width = parseInt(style.width.replace(/px$/, ''), 10);
|
|
|
|
this.prev_pageX = ev.pageX;
|
|
|
|
},
|
2020-05-15 14:33:31 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onMouseMove (ev) {
|
|
|
|
if (this.resizing) {
|
|
|
|
ev.preventDefault();
|
|
|
|
const delta = this.prev_pageX - ev.pageX;
|
|
|
|
this.resizeSidebarView(delta, ev.pageX);
|
|
|
|
this.prev_pageX = ev.pageX;
|
|
|
|
}
|
|
|
|
},
|
2020-03-18 19:32:03 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onMouseUp (ev) {
|
|
|
|
if (this.resizing) {
|
|
|
|
ev.preventDefault();
|
|
|
|
this.resizing = false;
|
|
|
|
this.el.removeEventListener('mousemove', this.onMouseMove);
|
|
|
|
this.el.removeEventListener('mouseup', this.onMouseUp);
|
2020-10-23 20:49:10 +02:00
|
|
|
const sidebar_el = this.el.querySelector('converse-muc-sidebar');
|
|
|
|
const element_position = sidebar_el.getBoundingClientRect();
|
2020-09-09 14:37:43 +02:00
|
|
|
const occupants_width = this.calculateSidebarWidth(element_position, 0);
|
2020-12-06 20:42:12 +01:00
|
|
|
const attrs = { occupants_width };
|
2020-09-09 14:37:43 +02:00
|
|
|
_converse.connection.connected ? this.model.save(attrs) : this.model.set(attrs);
|
|
|
|
}
|
|
|
|
},
|
2018-02-21 22:29:21 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
resizeSidebarView (delta, current_mouse_position) {
|
2020-10-23 20:49:10 +02:00
|
|
|
const sidebar_el = this.el.querySelector('converse-muc-sidebar');
|
|
|
|
const element_position = sidebar_el.getBoundingClientRect();
|
2020-09-09 14:37:43 +02:00
|
|
|
if (this.is_minimum) {
|
|
|
|
this.is_minimum = element_position.left < current_mouse_position;
|
|
|
|
} else if (this.is_maximum) {
|
|
|
|
this.is_maximum = element_position.left > current_mouse_position;
|
|
|
|
} else {
|
|
|
|
const occupants_width = this.calculateSidebarWidth(element_position, delta);
|
2020-12-06 20:42:12 +01:00
|
|
|
sidebar_el.style.flex = '0 0 ' + occupants_width + 'px';
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
},
|
2020-02-10 11:23:55 +01:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
calculateSidebarWidth (element_position, delta) {
|
2020-09-09 14:37:43 +02:00
|
|
|
let occupants_width = element_position.width + delta;
|
|
|
|
const room_width = this.el.clientWidth;
|
|
|
|
// keeping display in boundaries
|
2020-12-06 20:42:12 +01:00
|
|
|
if (occupants_width < room_width * 0.2) {
|
2020-09-09 14:37:43 +02:00
|
|
|
// set pixel to 20% width
|
2020-12-06 20:42:12 +01:00
|
|
|
occupants_width = room_width * 0.2;
|
2020-09-09 14:37:43 +02:00
|
|
|
this.is_minimum = true;
|
2020-12-06 20:42:12 +01:00
|
|
|
} else if (occupants_width > room_width * 0.75) {
|
2020-09-09 14:37:43 +02:00
|
|
|
// set pixel to 75% width
|
2020-12-06 20:42:12 +01:00
|
|
|
occupants_width = room_width * 0.75;
|
2020-09-09 14:37:43 +02:00
|
|
|
this.is_maximum = true;
|
2020-12-06 20:42:12 +01:00
|
|
|
} else if (room_width - occupants_width < 250) {
|
2020-09-09 14:37:43 +02:00
|
|
|
// resize occupants if chat-area becomes smaller than 250px (min-width property set in css)
|
|
|
|
occupants_width = room_width - 250;
|
|
|
|
this.is_maximum = true;
|
|
|
|
} else {
|
|
|
|
this.is_maximum = false;
|
|
|
|
this.is_minimum = false;
|
|
|
|
}
|
|
|
|
return occupants_width;
|
|
|
|
},
|
2019-04-10 23:25:14 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
getAutoCompleteList () {
|
2020-12-06 20:42:12 +01:00
|
|
|
return this.model.getAllKnownNicknames().map(nick => ({ 'label': nick, 'value': `@${nick}` }));
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2019-12-04 16:01:34 +01:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
getAutoCompleteListItem (text, input) {
|
2020-09-09 14:37:43 +02:00
|
|
|
input = input.trim();
|
2020-12-06 20:42:12 +01:00
|
|
|
const element = document.createElement('li');
|
|
|
|
element.setAttribute('aria-selected', 'false');
|
2019-12-04 16:01:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
if (api.settings.get('muc_mention_autocomplete_show_avatar')) {
|
2020-12-06 20:42:12 +01:00
|
|
|
const img = document.createElement('img');
|
|
|
|
let dataUri = 'data:' + _converse.DEFAULT_IMAGE_TYPE + ';base64,' + _converse.DEFAULT_IMAGE;
|
2019-12-04 16:01:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
if (_converse.vcards) {
|
2020-12-06 20:42:12 +01:00
|
|
|
const vcard = _converse.vcards.findWhere({ 'nickname': text });
|
|
|
|
if (vcard) dataUri = 'data:' + vcard.get('image_type') + ';base64,' + vcard.get('image');
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
2019-12-04 16:01:34 +01:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
img.setAttribute('src', dataUri);
|
|
|
|
img.setAttribute('width', '22');
|
|
|
|
img.setAttribute('class', 'avatar avatar-autocomplete');
|
2020-09-09 14:37:43 +02:00
|
|
|
element.appendChild(img);
|
|
|
|
}
|
2019-12-04 16:01:34 +01:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
const regex = new RegExp('(' + input + ')', 'ig');
|
2020-09-09 14:37:43 +02:00
|
|
|
const parts = input ? text.split(regex) : [text];
|
2019-12-04 16:01:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
parts.forEach(txt => {
|
|
|
|
if (input && txt.match(regex)) {
|
2020-12-06 20:42:12 +01:00
|
|
|
const match = document.createElement('mark');
|
|
|
|
match.textContent = txt;
|
|
|
|
element.appendChild(match);
|
2020-09-09 14:37:43 +02:00
|
|
|
} else {
|
2020-12-06 20:42:12 +01:00
|
|
|
element.appendChild(document.createTextNode(txt));
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
});
|
2019-05-15 14:47:04 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
return element;
|
|
|
|
},
|
2019-05-27 22:45:51 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
initMentionAutoComplete () {
|
|
|
|
this.mention_auto_complete = new _converse.AutoComplete(this.el, {
|
|
|
|
'auto_first': true,
|
|
|
|
'auto_evaluate': false,
|
|
|
|
'min_chars': api.settings.get('muc_mention_autocomplete_min_chars'),
|
|
|
|
'match_current_word': true,
|
|
|
|
'list': () => this.getAutoCompleteList(),
|
2020-12-06 20:42:12 +01:00
|
|
|
'filter':
|
|
|
|
api.settings.get('muc_mention_autocomplete_filter') == 'contains'
|
|
|
|
? _converse.FILTER_CONTAINS
|
|
|
|
: _converse.FILTER_STARTSWITH,
|
|
|
|
'ac_triggers': ['Tab', '@'],
|
2020-09-09 14:37:43 +02:00
|
|
|
'include_triggers': [],
|
|
|
|
'item': this.getAutoCompleteListItem
|
|
|
|
});
|
|
|
|
this.mention_auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false));
|
|
|
|
},
|
2019-11-11 15:27:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Get the nickname value from the form and then join the groupchat with it.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#submitNickname
|
|
|
|
* @param { Event }
|
|
|
|
*/
|
|
|
|
submitNickname (ev) {
|
|
|
|
ev.preventDefault();
|
|
|
|
const nick = ev.target.nick.value.trim();
|
|
|
|
nick && this.model.join(nick);
|
|
|
|
},
|
2019-11-11 15:27:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onKeyDown (ev) {
|
|
|
|
if (this.mention_auto_complete.onKeyDown(ev)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return _converse.ChatBoxView.prototype.onKeyDown.call(this, ev);
|
|
|
|
},
|
2019-11-11 15:27:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onKeyUp (ev) {
|
|
|
|
this.mention_auto_complete.evaluate(ev);
|
|
|
|
return _converse.ChatBoxView.prototype.onKeyUp.call(this, ev);
|
|
|
|
},
|
2019-11-11 15:27:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
async onMessageRetractButtonClicked (message) {
|
2020-12-06 20:42:12 +01:00
|
|
|
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.'
|
|
|
|
);
|
2019-11-11 15:27:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
if (message.mayBeRetracted()) {
|
|
|
|
const messages = [__('Are you sure you want to retract this message?')];
|
|
|
|
if (api.settings.get('show_retraction_warning')) {
|
|
|
|
messages[1] = retraction_warning;
|
|
|
|
}
|
|
|
|
!!(await api.confirm(__('Confirm'), messages)) && this.model.retractOwnMessage(message);
|
|
|
|
} else if (await message.mayBeModerated()) {
|
|
|
|
if (message.get('sender') === 'me') {
|
|
|
|
let messages = [__('Are you sure you want to retract this message?')];
|
|
|
|
if (api.settings.get('show_retraction_warning')) {
|
2020-12-06 20:42:12 +01:00
|
|
|
messages = [messages[0], retraction_warning, messages[1]];
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
!!(await api.confirm(__('Confirm'), messages)) && this.retractOtherMessage(message);
|
|
|
|
} else {
|
|
|
|
let messages = [
|
|
|
|
__('You are about to retract this message.'),
|
|
|
|
__('You may optionally include a message, explaining the reason for the retraction.')
|
|
|
|
];
|
|
|
|
if (api.settings.get('show_retraction_warning')) {
|
2020-12-06 20:42:12 +01:00
|
|
|
messages = [messages[0], retraction_warning, messages[1]];
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
const reason = await api.prompt(__('Message Retraction'), messages, __('Optional reason'));
|
|
|
|
reason !== false && this.retractOtherMessage(message, reason);
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const err_msg = __(`Sorry, you're not allowed to retract this message`);
|
|
|
|
api.alert('error', __('Error'), err_msg);
|
|
|
|
}
|
|
|
|
},
|
2019-11-11 15:27:34 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Retract someone else's message in this groupchat.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#retractOtherMessage
|
|
|
|
* @param { _converse.Message } message - The message which we're retracting.
|
|
|
|
* @param { string } [reason] - The reason for retracting the message.
|
|
|
|
*/
|
|
|
|
async retractOtherMessage (message, reason) {
|
|
|
|
const result = await this.model.retractOtherMessage(message, reason);
|
|
|
|
if (result === null) {
|
|
|
|
const err_msg = __(`A timeout occurred while trying to retract the message`);
|
|
|
|
api.alert('error', __('Error'), err_msg);
|
|
|
|
log(err_msg, Strophe.LogLevel.WARN);
|
|
|
|
} else if (u.isErrorStanza(result)) {
|
|
|
|
const err_msg = __(`Sorry, you're not allowed to retract this message.`);
|
|
|
|
api.alert('error', __('Error'), err_msg);
|
|
|
|
log(err_msg, Strophe.LogLevel.WARN);
|
|
|
|
log(result, Strophe.LogLevel.WARN);
|
|
|
|
}
|
|
|
|
},
|
2018-02-22 15:51:44 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
showModeratorToolsModal (affiliation) {
|
|
|
|
if (!this.verifyRoles(['moderator'])) {
|
|
|
|
return;
|
|
|
|
}
|
2020-12-02 18:37:32 +01:00
|
|
|
let modal = api.modal.get(ModeratorToolsModal.id);
|
|
|
|
if (modal) {
|
|
|
|
modal.model.set('affiliation', affiliation);
|
2020-09-09 14:37:43 +02:00
|
|
|
} else {
|
2020-12-06 20:42:12 +01:00
|
|
|
const model = new Model({ 'affiliation': affiliation });
|
|
|
|
modal = api.modal.create(ModeratorToolsModal, { model, _converse, 'chatroomview': this });
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
2020-12-02 18:37:32 +01:00
|
|
|
modal.show();
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2019-11-28 17:01:08 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
showRoomDetailsModal (ev) {
|
|
|
|
ev.preventDefault();
|
2020-12-06 20:42:12 +01:00
|
|
|
api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2018-02-21 22:29:21 +01:00
|
|
|
|
2020-12-01 16:31:57 +01:00
|
|
|
showOccupantDetailsModal (ev, message) {
|
|
|
|
ev.preventDefault();
|
2020-12-06 20:42:12 +01:00
|
|
|
api.modal.show(OccupantModal, { 'model': message.occupant }, ev);
|
2020-12-01 16:31:57 +01:00
|
|
|
},
|
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
showChatStateNotification (message) {
|
|
|
|
if (message.get('sender') === 'me') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return _converse.ChatBoxView.prototype.showChatStateNotification.apply(this, arguments);
|
|
|
|
},
|
2018-02-21 22:29:21 +01:00
|
|
|
|
2020-10-23 20:49:10 +02:00
|
|
|
shouldShowSidebar () {
|
2020-12-06 20:42:12 +01:00
|
|
|
return (
|
|
|
|
!this.model.get('hidden_occupants') &&
|
|
|
|
this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED
|
|
|
|
);
|
2020-10-23 20:49:10 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
onSidebarToggle () {
|
|
|
|
this.renderToolbar();
|
|
|
|
this.el.querySelector('.occupants')?.setVisibility();
|
|
|
|
},
|
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onOccupantAffiliationChanged (occupant) {
|
|
|
|
if (occupant.get('jid') === _converse.bare_jid) {
|
|
|
|
this.renderHeading();
|
|
|
|
}
|
|
|
|
},
|
2019-09-24 15:33:41 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onOccupantRoleChanged (occupant) {
|
|
|
|
if (occupant.get('jid') === _converse.bare_jid) {
|
|
|
|
this.renderBottomPanel();
|
|
|
|
}
|
|
|
|
},
|
2019-09-24 15:33:41 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Returns a list of objects which represent buttons for the groupchat header.
|
|
|
|
* @emits _converse#getHeadingButtons
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#getHeadingButtons
|
|
|
|
*/
|
|
|
|
getHeadingButtons (subject_hidden) {
|
|
|
|
const buttons = [];
|
|
|
|
buttons.push({
|
|
|
|
'i18n_text': __('Details'),
|
|
|
|
'i18n_title': __('Show more information about this groupchat'),
|
|
|
|
'handler': ev => this.showRoomDetailsModal(ev),
|
2020-12-03 09:58:22 +01:00
|
|
|
'a_class': 'show-muc-details-modal',
|
2020-09-09 14:37:43 +02:00
|
|
|
'icon_class': 'fa-info-circle',
|
|
|
|
'name': 'details'
|
|
|
|
});
|
2019-07-04 14:12:12 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
if (this.model.getOwnAffiliation() === 'owner') {
|
|
|
|
buttons.push({
|
|
|
|
'i18n_text': __('Configure'),
|
|
|
|
'i18n_title': __('Configure this groupchat'),
|
|
|
|
'handler': ev => this.getAndRenderConfigurationForm(ev),
|
|
|
|
'a_class': 'configure-chatroom-button',
|
|
|
|
'icon_class': 'fa-wrench',
|
|
|
|
'name': 'configure'
|
|
|
|
});
|
|
|
|
}
|
2018-06-04 19:53:33 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
if (this.model.invitesAllowed()) {
|
|
|
|
buttons.push({
|
|
|
|
'i18n_text': __('Invite'),
|
|
|
|
'i18n_title': __('Invite someone to join this groupchat'),
|
|
|
|
'handler': ev => this.showInviteModal(ev),
|
|
|
|
'a_class': 'open-invite-modal',
|
|
|
|
'icon_class': 'fa-user-plus',
|
|
|
|
'name': 'invite'
|
|
|
|
});
|
|
|
|
}
|
2018-06-04 19:53:33 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
const subject = this.model.get('subject');
|
|
|
|
if (subject && subject.text) {
|
|
|
|
buttons.push({
|
|
|
|
'i18n_text': subject_hidden ? __('Show topic') : __('Hide topic'),
|
2020-12-06 20:42:12 +01:00
|
|
|
'i18n_title': subject_hidden
|
|
|
|
? __('Show the topic message in the heading')
|
|
|
|
: __('Hide the topic in the heading'),
|
2020-09-09 14:37:43 +02:00
|
|
|
'handler': ev => this.toggleTopic(ev),
|
|
|
|
'a_class': 'hide-topic',
|
|
|
|
'icon_class': 'fa-minus-square',
|
|
|
|
'name': 'toggle-topic'
|
|
|
|
});
|
|
|
|
}
|
2019-07-10 09:47:13 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
const conn_status = this.model.session.get('connection_status');
|
|
|
|
if (conn_status === converse.ROOMSTATUS.ENTERED) {
|
|
|
|
const allowed_commands = this.getAllowedCommands();
|
|
|
|
if (allowed_commands.includes('modtools')) {
|
2020-04-15 13:59:55 +02:00
|
|
|
buttons.push({
|
2020-09-09 14:37:43 +02:00
|
|
|
'i18n_text': __('Moderate'),
|
|
|
|
'i18n_title': __('Moderate this groupchat'),
|
|
|
|
'handler': () => this.showModeratorToolsModal(),
|
|
|
|
'a_class': 'moderate-chatroom-button',
|
|
|
|
'icon_class': 'fa-user-cog',
|
|
|
|
'name': 'moderate'
|
2020-04-15 13:59:55 +02:00
|
|
|
});
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
if (allowed_commands.includes('destroy')) {
|
|
|
|
buttons.push({
|
|
|
|
'i18n_text': __('Destroy'),
|
|
|
|
'i18n_title': __('Remove this groupchat'),
|
|
|
|
'handler': ev => this.destroy(ev),
|
|
|
|
'a_class': 'destroy-chatroom-button',
|
|
|
|
'icon_class': 'fa-trash',
|
|
|
|
'name': 'destroy'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-04-15 13:59:55 +02:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
if (!api.settings.get('singleton')) {
|
2020-09-09 14:37:43 +02:00
|
|
|
buttons.push({
|
|
|
|
'i18n_text': __('Leave'),
|
|
|
|
'i18n_title': __('Leave and close this groupchat'),
|
|
|
|
'handler': async ev => {
|
|
|
|
ev.stopPropagation();
|
|
|
|
const messages = [__('Are you sure you want to leave this groupchat?')];
|
|
|
|
const result = await api.confirm(__('Confirm'), messages);
|
|
|
|
result && this.close(ev);
|
|
|
|
},
|
|
|
|
'a_class': 'close-chatbox-button',
|
2020-12-06 20:42:12 +01:00
|
|
|
'standalone': api.settings.get('view_mode') === 'overlayed',
|
2020-09-09 14:37:43 +02:00
|
|
|
'icon_class': 'fa-sign-out-alt',
|
|
|
|
'name': 'signout'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return _converse.api.hook('getHeadingButtons', this, buttons);
|
|
|
|
},
|
2020-02-10 11:23:55 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Returns the groupchat heading TemplateResult to be rendered.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#generateHeadingTemplate
|
|
|
|
*/
|
|
|
|
async generateHeadingTemplate () {
|
|
|
|
const subject_hidden = await this.model.isSubjectHidden();
|
|
|
|
const heading_btns = await this.getHeadingButtons(subject_hidden);
|
|
|
|
const standalone_btns = heading_btns.filter(b => b.standalone);
|
|
|
|
const dropdown_btns = heading_btns.filter(b => !b.standalone);
|
|
|
|
return tpl_chatroom_head(
|
|
|
|
Object.assign(this.model.toJSON(), {
|
|
|
|
_converse,
|
|
|
|
subject_hidden,
|
|
|
|
'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)),
|
|
|
|
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b)),
|
2020-12-06 20:42:12 +01:00
|
|
|
'title': this.model.getDisplayName()
|
|
|
|
})
|
|
|
|
);
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2020-04-15 09:13:50 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
toggleTopic () {
|
|
|
|
this.model.toggleSubjectHiddenState();
|
|
|
|
},
|
2020-04-15 09:13:50 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
showInviteModal (ev) {
|
|
|
|
ev.preventDefault();
|
2020-12-06 20:42:12 +01:00
|
|
|
api.modal.show(MUCInviteModal, { 'model': new Model(), 'chatroomview': this }, ev);
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2020-02-07 14:58:26 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Callback method that gets called after the chat has become visible.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#afterShown
|
|
|
|
*/
|
|
|
|
afterShown () {
|
|
|
|
// Override from converse-chatview, specifically to avoid
|
|
|
|
// the 'active' chat state from being sent out prematurely.
|
|
|
|
// This is instead done in `onConnectionStatusChanged` below.
|
|
|
|
if (u.isPersistableModel(this.model)) {
|
|
|
|
this.model.clearUnreadMsgCounter();
|
|
|
|
}
|
|
|
|
this.scrollDown();
|
|
|
|
},
|
2020-02-10 11:23:55 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onConnectionStatusChanged () {
|
|
|
|
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) {
|
|
|
|
this.renderPasswordForm();
|
|
|
|
} else if (conn_status === converse.ROOMSTATUS.CONNECTING) {
|
|
|
|
this.showSpinner();
|
|
|
|
} else if (conn_status === converse.ROOMSTATUS.ENTERED) {
|
|
|
|
this.renderBottomPanel();
|
|
|
|
this.hideSpinner();
|
|
|
|
this.maybeFocus();
|
|
|
|
} else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
|
|
|
|
this.showDisconnectMessage();
|
|
|
|
} else if (conn_status === converse.ROOMSTATUS.DESTROYED) {
|
|
|
|
this.showDestroyedMessage();
|
|
|
|
}
|
|
|
|
},
|
2020-02-10 11:23:55 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
getToolbarOptions () {
|
2020-12-06 20:42:12 +01:00
|
|
|
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
|
|
|
|
});
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2020-02-10 11:23:55 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Closes this chat box, which implies leaving the groupchat as well.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#close
|
|
|
|
*/
|
|
|
|
async close () {
|
|
|
|
this.hide();
|
2020-12-06 20:42:12 +01:00
|
|
|
if (_converse.router.history.getFragment() === 'converse/room?jid=' + this.model.get('jid')) {
|
2020-09-09 14:37:43 +02:00
|
|
|
_converse.router.navigate('');
|
|
|
|
}
|
|
|
|
await this.model.leave();
|
|
|
|
return _converse.ChatBoxView.prototype.close.apply(this, arguments);
|
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Hide the right sidebar containing the chat occupants.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#hideOccupants
|
|
|
|
*/
|
|
|
|
hideOccupants (ev) {
|
|
|
|
if (ev) {
|
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.save({ 'hidden_occupants': true });
|
2020-09-09 14:37:43 +02:00
|
|
|
this.scrollDown();
|
|
|
|
},
|
2018-08-10 14:09:21 +02:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
verifyRoles (roles, occupant, show_error = true) {
|
2020-09-09 14:37:43 +02:00
|
|
|
if (!Array.isArray(roles)) {
|
|
|
|
throw new TypeError('roles must be an Array');
|
|
|
|
}
|
|
|
|
if (!roles.length) {
|
|
|
|
return true;
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
occupant = occupant || this.model.occupants.findWhere({ 'jid': _converse.bare_jid });
|
2020-09-09 14:37:43 +02:00
|
|
|
if (occupant) {
|
|
|
|
const role = occupant.get('role');
|
|
|
|
if (roles.includes(role)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (show_error) {
|
|
|
|
const message = __('Forbidden: you do not have the necessary role in order to do that.');
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
2018-08-10 14:09:21 +02:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
verifyAffiliations (affiliations, occupant, show_error = true) {
|
2020-09-09 14:37:43 +02:00
|
|
|
if (!Array.isArray(affiliations)) {
|
|
|
|
throw new TypeError('affiliations must be an Array');
|
|
|
|
}
|
|
|
|
if (!affiliations.length) {
|
|
|
|
return true;
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
occupant = occupant || this.model.occupants.findWhere({ 'jid': _converse.bare_jid });
|
2020-09-09 14:37:43 +02:00
|
|
|
if (occupant) {
|
|
|
|
const a = occupant.get('affiliation');
|
|
|
|
if (affiliations.includes(a)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (show_error) {
|
|
|
|
const message = __('Forbidden: you do not have the necessary affiliation in order to do that.');
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
validateRoleOrAffiliationChangeArgs (command, args) {
|
|
|
|
if (!args) {
|
|
|
|
const message = __(
|
|
|
|
'Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.',
|
|
|
|
command
|
|
|
|
);
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
getNickOrJIDFromCommandArgs (args) {
|
|
|
|
if (u.isValidJID(args.trim())) {
|
|
|
|
return args.trim();
|
|
|
|
}
|
|
|
|
if (!args.startsWith('@')) {
|
2020-12-06 20:42:12 +01:00
|
|
|
args = '@' + args;
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
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");
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (references.length > 1) {
|
2020-12-06 20:42:12 +01:00
|
|
|
const message = __('Error: found multiple groupchat participant based on your arguments');
|
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
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");
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
return nick_or_jid;
|
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
setAffiliation (command, args, required_affiliations) {
|
|
|
|
const affiliation = COMMAND_TO_AFFILIATION[command];
|
|
|
|
if (!affiliation) {
|
|
|
|
throw Error(`ChatRoomView#setAffiliation called with invalid command: ${command}`);
|
|
|
|
}
|
|
|
|
if (!this.verifyAffiliations(required_affiliations)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this.validateRoleOrAffiliationChangeArgs(command, args)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const nick_or_jid = this.getNickOrJIDFromCommandArgs(args);
|
|
|
|
if (!nick_or_jid) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-02-21 22:29:21 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
let jid;
|
|
|
|
const reason = args.split(nick_or_jid, 2)[1].trim();
|
|
|
|
const occupant = this.model.getOccupant(nick_or_jid);
|
|
|
|
if (occupant) {
|
|
|
|
jid = occupant.get('jid');
|
|
|
|
} else {
|
|
|
|
if (u.isValidJID(nick_or_jid)) {
|
|
|
|
jid = nick_or_jid;
|
|
|
|
} else {
|
|
|
|
const message = __(
|
2020-12-06 20:42:12 +01:00
|
|
|
"Couldn't find a participant with that nickname. " + 'They might have left the groupchat.'
|
2020-09-09 14:37:43 +02:00
|
|
|
);
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const attrs = { jid, reason };
|
|
|
|
if (occupant && api.settings.get('auto_register_muc_nickname')) {
|
|
|
|
attrs['nick'] = occupant.get('nick');
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model
|
|
|
|
.setAffiliation(affiliation, [attrs])
|
2020-09-09 14:37:43 +02:00
|
|
|
.then(() => this.model.occupants.fetchMembers())
|
|
|
|
.catch(err => this.onCommandError(err));
|
|
|
|
},
|
2019-05-26 17:53:32 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
getReason (args) {
|
2020-12-06 20:42:12 +01:00
|
|
|
return args.includes(',') ? args.slice(args.indexOf(',') + 1).trim() : null;
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2019-05-26 17:53:32 +02:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
setRole (command, args, required_affiliations = [], required_roles = []) {
|
2020-09-09 14:37:43 +02:00
|
|
|
/* Check that a command to change a groupchat user's role or
|
|
|
|
* affiliation has anough arguments.
|
|
|
|
*/
|
|
|
|
const role = COMMAND_TO_ROLE[command];
|
|
|
|
if (!role) {
|
|
|
|
throw Error(`ChatRoomView#setRole called with invalid command: ${command}`);
|
|
|
|
}
|
|
|
|
if (!this.verifyAffiliations(required_affiliations) || !this.verifyRoles(required_roles)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this.validateRoleOrAffiliationChangeArgs(command, args)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const nick_or_jid = this.getNickOrJIDFromCommandArgs(args);
|
|
|
|
if (!nick_or_jid) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const reason = args.split(nick_or_jid, 2)[1].trim();
|
|
|
|
// We're guaranteed to have an occupant due to getNickOrJIDFromCommandArgs
|
|
|
|
const occupant = this.model.getOccupant(nick_or_jid);
|
|
|
|
this.model.setRole(occupant, role, reason, undefined, this.onCommandError.bind(this));
|
|
|
|
return true;
|
|
|
|
},
|
2020-04-19 06:49:03 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onCommandError (err) {
|
|
|
|
log.fatal(err);
|
|
|
|
const message =
|
2020-12-06 20:42:12 +01:00
|
|
|
__('Sorry, an error happened while running the command.') +
|
|
|
|
' ' +
|
2020-09-09 14:37:43 +02:00
|
|
|
__("Check your browser's developer console for details.");
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2019-05-26 17:53:32 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
getAllowedCommands () {
|
|
|
|
let allowed_commands = ['clear', 'help', 'me', 'nick', 'register'];
|
|
|
|
if (this.model.config.get('changesubject') || ['owner', 'admin'].includes(this.model.getOwnAffiliation())) {
|
|
|
|
allowed_commands = [...allowed_commands, ...['subject', 'topic']];
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
const occupant = this.model.occupants.findWhere({ 'jid': _converse.bare_jid });
|
2020-09-09 14:37:43 +02:00
|
|
|
if (this.verifyAffiliations(['owner'], occupant, false)) {
|
|
|
|
allowed_commands = allowed_commands.concat(OWNER_COMMANDS).concat(ADMIN_COMMANDS);
|
|
|
|
} else if (this.verifyAffiliations(['admin'], occupant, false)) {
|
|
|
|
allowed_commands = allowed_commands.concat(ADMIN_COMMANDS);
|
|
|
|
}
|
|
|
|
if (this.verifyRoles(['moderator'], occupant, false)) {
|
|
|
|
allowed_commands = allowed_commands.concat(MODERATOR_COMMANDS).concat(VISITOR_COMMANDS);
|
|
|
|
} else if (!this.verifyRoles(['visitor', 'participant', 'moderator'], occupant, false)) {
|
|
|
|
allowed_commands = allowed_commands.concat(VISITOR_COMMANDS);
|
|
|
|
}
|
|
|
|
allowed_commands.sort();
|
2019-05-26 17:53:32 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
if (Array.isArray(api.settings.get('muc_disable_slash_commands'))) {
|
|
|
|
return allowed_commands.filter(c => !api.settings.get('muc_disable_slash_commands').includes(c));
|
|
|
|
} else {
|
|
|
|
return allowed_commands;
|
|
|
|
}
|
|
|
|
},
|
2018-05-30 16:55:14 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
async destroy () {
|
|
|
|
const messages = [__('Are you sure you want to destroy this groupchat?')];
|
2020-12-06 20:42:12 +01:00
|
|
|
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')
|
|
|
|
}
|
|
|
|
];
|
2020-09-09 14:37:43 +02:00
|
|
|
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;
|
2020-12-06 20:42:12 +01:00
|
|
|
return this.model.sendDestroyIQ(reason, newjid).then(() => this.close());
|
2020-09-09 14:37:43 +02:00
|
|
|
} catch (e) {
|
|
|
|
log.error(e);
|
|
|
|
}
|
|
|
|
},
|
2018-08-09 13:07:32 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
parseMessageForCommands (text) {
|
2020-12-06 20:42:12 +01:00
|
|
|
if (
|
|
|
|
api.settings.get('muc_disable_slash_commands') &&
|
|
|
|
!Array.isArray(api.settings.get('muc_disable_slash_commands'))
|
|
|
|
) {
|
2020-09-09 14:37:43 +02:00
|
|
|
return _converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments);
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
text = text.replace(/^\s*/, '');
|
2020-09-09 14:37:43 +02:00
|
|
|
const command = (text.match(/^\/([a-zA-Z]*) ?/) || ['']).pop().toLowerCase();
|
|
|
|
if (!command) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
const args = text.slice(('/' + command).length + 1).trim();
|
2020-09-09 14:37:43 +02:00
|
|
|
if (!this.getAllowedCommands().includes(command)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (command) {
|
|
|
|
case 'admin': {
|
|
|
|
this.setAffiliation(command, args, ['owner']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'ban': {
|
|
|
|
this.setAffiliation(command, args, ['admin', 'owner']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'modtools': {
|
|
|
|
this.showModeratorToolsModal(args);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'deop': {
|
|
|
|
// FIXME: /deop only applies to setting a moderators
|
|
|
|
// role to "participant" (which only admin/owner can
|
|
|
|
// do). Moderators can however set non-moderator's role
|
|
|
|
// to participant (e.g. visitor => participant).
|
|
|
|
// Currently we don't distinguish between these two
|
|
|
|
// cases.
|
|
|
|
this.setRole(command, args, ['admin', 'owner']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'destroy': {
|
|
|
|
if (!this.verifyAffiliations(['owner'])) {
|
|
|
|
break;
|
2019-07-04 14:12:12 +02:00
|
|
|
}
|
2020-09-09 14:37:43 +02:00
|
|
|
this.destroy().catch(e => this.onCommandError(e));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'help': {
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.set({ 'show_help_messages': true });
|
2020-09-09 14:37:43 +02:00
|
|
|
break;
|
2020-12-06 20:42:12 +01:00
|
|
|
}
|
|
|
|
case 'kick': {
|
2020-09-09 14:37:43 +02:00
|
|
|
this.setRole(command, args, [], ['moderator']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'mute': {
|
|
|
|
this.setRole(command, args, [], ['moderator']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'member': {
|
|
|
|
this.setAffiliation(command, args, ['admin', 'owner']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'nick': {
|
|
|
|
if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) {
|
|
|
|
break;
|
|
|
|
} else if (args.length === 0) {
|
|
|
|
// e.g. Your nickname is "coolguy69"
|
|
|
|
const message = __('Your nickname is "%1$s"', this.model.get('nick'));
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model.createMessage({ message, 'type': 'error' });
|
2020-02-21 13:21:25 +01:00
|
|
|
} else {
|
2020-09-09 14:37:43 +02:00
|
|
|
const jid = Strophe.getBareJidFromJid(this.model.get('jid'));
|
2020-12-06 20:42:12 +01:00
|
|
|
api.send(
|
|
|
|
$pres({
|
|
|
|
from: _converse.connection.jid,
|
|
|
|
to: `${jid}/${args}`,
|
|
|
|
id: u.getUniqueId()
|
|
|
|
}).tree()
|
|
|
|
);
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'owner':
|
|
|
|
this.setAffiliation(command, args, ['owner']);
|
|
|
|
break;
|
|
|
|
case 'op': {
|
|
|
|
this.setRole(command, args, ['admin', 'owner']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'register': {
|
|
|
|
if (args.length > 1) {
|
|
|
|
this.model.createMessage({
|
|
|
|
'message': __('Error: invalid number of arguments'),
|
|
|
|
'type': 'error'
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.model.registerNickname().then(err_msg => {
|
2020-12-06 20:42:12 +01:00
|
|
|
err_msg && this.model.createMessage({ 'message': err_msg, 'type': 'error' });
|
2020-09-09 14:37:43 +02:00
|
|
|
});
|
2020-02-21 13:21:25 +01:00
|
|
|
}
|
2020-09-09 14:37:43 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'revoke': {
|
|
|
|
this.setAffiliation(command, args, ['admin', 'owner']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'topic':
|
|
|
|
case 'subject':
|
|
|
|
this.model.setSubject(args);
|
|
|
|
break;
|
|
|
|
case 'voice': {
|
|
|
|
this.setRole(command, args, [], ['moderator']);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return _converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
2019-07-04 14:12:12 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Renders a form given an IQ stanza containing the current
|
|
|
|
* groupchat configuration.
|
|
|
|
* Returns a promise which resolves once the user has
|
|
|
|
* either submitted the form, or canceled it.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#renderConfigurationForm
|
|
|
|
* @param { XMLElement } stanza: The IQ stanza containing the groupchat config.
|
|
|
|
*/
|
|
|
|
renderConfigurationForm (stanza) {
|
|
|
|
this.hideChatRoomContents();
|
|
|
|
this.model.save('config_stanza', stanza.outerHTML);
|
|
|
|
if (!this.config_form) {
|
|
|
|
this.config_form = new _converse.MUCConfigForm({
|
|
|
|
'model': this.model,
|
|
|
|
'chatroomview': this
|
|
|
|
});
|
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
container_el.insertAdjacentElement('beforeend', this.config_form.el);
|
|
|
|
}
|
|
|
|
u.showElement(this.config_form.el);
|
|
|
|
},
|
2020-02-22 20:14:12 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Renders a form which allows the user to choose theirnickname.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#renderNicknameForm
|
|
|
|
*/
|
|
|
|
renderNicknameForm () {
|
2020-10-01 16:26:26 +02:00
|
|
|
const tpl_result = tpl_muc_nickname_form(this.model.toJSON());
|
2020-09-09 14:37:43 +02:00
|
|
|
if (api.settings.get('muc_show_logs_before_join')) {
|
|
|
|
const container = this.el.querySelector('.muc-bottom-panel');
|
2020-10-01 16:26:26 +02:00
|
|
|
render(tpl_result, container);
|
2020-09-09 14:37:43 +02:00
|
|
|
u.addClass('muc-bottom-panel--nickname', container);
|
|
|
|
} else {
|
|
|
|
const form = this.el.querySelector('.muc-nickname-form');
|
2020-10-01 16:26:26 +02:00
|
|
|
const form_el = u.getElementFromTemplateResult(tpl_result);
|
2020-09-09 14:37:43 +02:00
|
|
|
if (form) {
|
|
|
|
sizzle('.spinner', this.el).forEach(u.removeElement);
|
2020-09-26 13:13:50 +02:00
|
|
|
form.outerHTML = form_el.outerHTML;
|
2020-09-09 14:37:43 +02:00
|
|
|
} else {
|
|
|
|
this.hideChatRoomContents();
|
|
|
|
const container = this.el.querySelector('.chatroom-body');
|
2020-09-26 13:13:50 +02:00
|
|
|
container.insertAdjacentElement('beforeend', form_el);
|
2020-09-09 14:37:43 +02:00
|
|
|
}
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
u.safeSave(this.model.session, { 'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED });
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2019-05-14 20:31:43 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Remove the configuration form without submitting and return to the chat view.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#closeForm
|
|
|
|
*/
|
|
|
|
closeForm () {
|
|
|
|
sizzle('.chatroom-form-container', this.el).forEach(e => u.addClass('hidden', e));
|
|
|
|
this.renderAfterTransition();
|
|
|
|
},
|
2018-02-21 22:29:21 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Start the process of configuring a groupchat, either by
|
|
|
|
* rendering a configuration form, or by auto-configuring
|
|
|
|
* based on the "roomconfig" data stored on the
|
|
|
|
* {@link _converse.ChatRoom}.
|
|
|
|
* Stores the new configuration on the {@link _converse.ChatRoom}
|
|
|
|
* once completed.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#getAndRenderConfigurationForm
|
|
|
|
* @param { Event } ev - DOM event that might be passed in if this
|
|
|
|
* method is called due to a user action. In this
|
|
|
|
* case, auto-configure won't happen, regardless of
|
|
|
|
* the settings.
|
|
|
|
*/
|
|
|
|
getAndRenderConfigurationForm () {
|
|
|
|
if (!this.config_form || !u.isVisible(this.config_form.el)) {
|
|
|
|
this.showSpinner();
|
2020-12-06 20:42:12 +01:00
|
|
|
this.model
|
|
|
|
.fetchRoomConfiguration()
|
2020-09-09 14:37:43 +02:00
|
|
|
.then(iq => this.renderConfigurationForm(iq))
|
|
|
|
.catch(e => log.error(e));
|
|
|
|
} else {
|
|
|
|
this.closeForm();
|
|
|
|
}
|
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
hideChatRoomContents () {
|
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
if (container_el !== null) {
|
|
|
|
[].forEach.call(container_el.children, child => child.classList.add('hidden'));
|
|
|
|
}
|
|
|
|
},
|
2019-11-28 17:01:08 +01:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
renderPasswordForm () {
|
|
|
|
this.hideChatRoomContents();
|
|
|
|
const message = this.model.get('password_validation_message');
|
|
|
|
this.model.save('password_validation_message', undefined);
|
|
|
|
|
|
|
|
if (!this.password_form) {
|
|
|
|
this.password_form = new _converse.MUCPasswordForm({
|
|
|
|
'model': new Model({
|
|
|
|
'validation_message': message
|
|
|
|
}),
|
2020-12-06 20:42:12 +01:00
|
|
|
'chatroomview': this
|
2020-09-09 14:37:43 +02:00
|
|
|
});
|
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
container_el.insertAdjacentElement('beforeend', this.password_form.el);
|
|
|
|
} else {
|
|
|
|
this.password_form.model.set('validation_message', message);
|
|
|
|
}
|
|
|
|
u.showElement(this.password_form.el);
|
|
|
|
this.model.session.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED);
|
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
showDestroyedMessage () {
|
|
|
|
u.hideElement(this.el.querySelector('.chat-area'));
|
|
|
|
u.hideElement(this.el.querySelector('.occupants'));
|
|
|
|
sizzle('.spinner', this.el).forEach(u.removeElement);
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
const reason = this.model.get('destroyed_reason');
|
|
|
|
const moved_jid = this.model.get('moved_jid');
|
|
|
|
this.model.save({
|
|
|
|
'destroyed_reason': undefined,
|
|
|
|
'moved_jid': undefined
|
|
|
|
});
|
|
|
|
const container = this.el.querySelector('.disconnect-container');
|
2020-09-26 13:13:50 +02:00
|
|
|
render(tpl_muc_destroyed(moved_jid, reason), container);
|
2020-09-09 14:37:43 +02:00
|
|
|
const switch_el = container.querySelector('a.switch-chat');
|
|
|
|
if (switch_el) {
|
|
|
|
switch_el.addEventListener('click', async ev => {
|
|
|
|
ev.preventDefault();
|
|
|
|
const room = await api.rooms.get(moved_jid, null, true);
|
|
|
|
room.maybeShow(true);
|
|
|
|
this.model.destroy();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
u.showElement(container);
|
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
showDisconnectMessage () {
|
|
|
|
const message = this.model.get('disconnection_message');
|
|
|
|
if (!message) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
u.hideElement(this.el.querySelector('.chat-area'));
|
|
|
|
u.hideElement(this.el.querySelector('.occupants'));
|
|
|
|
sizzle('.spinner', this.el).forEach(u.removeElement);
|
|
|
|
|
|
|
|
const messages = [message];
|
|
|
|
const actor = this.model.get('disconnection_actor');
|
|
|
|
if (actor) {
|
|
|
|
messages.push(__('This action was done by %1$s.', actor));
|
|
|
|
}
|
|
|
|
const reason = this.model.get('disconnection_reason');
|
|
|
|
if (reason) {
|
|
|
|
messages.push(__('The reason given is: "%1$s".', reason));
|
|
|
|
}
|
|
|
|
this.model.save({
|
|
|
|
'disconnection_message': undefined,
|
|
|
|
'disconnection_reason': undefined,
|
|
|
|
'disconnection_actor': undefined
|
|
|
|
});
|
|
|
|
const container = this.el.querySelector('.disconnect-container');
|
2020-09-26 13:13:50 +02:00
|
|
|
render(tpl_muc_disconnect(messages), container);
|
2020-09-09 14:37:43 +02:00
|
|
|
u.showElement(container);
|
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
onOccupantAdded (occupant) {
|
|
|
|
if (occupant.get('jid') === _converse.bare_jid) {
|
|
|
|
this.renderHeading();
|
|
|
|
this.renderBottomPanel();
|
|
|
|
}
|
|
|
|
},
|
2019-05-14 11:38:41 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Working backwards, get today's most recent join/leave notification
|
|
|
|
* from the same user (if any exists) after the most recent chat message.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#getPreviousJoinOrLeaveNotification
|
|
|
|
* @param {HTMLElement} el
|
|
|
|
* @param {string} nick
|
|
|
|
*/
|
|
|
|
getPreviousJoinOrLeaveNotification (el, nick) {
|
2020-12-06 20:42:12 +01:00
|
|
|
const today = new Date().toISOString().split('T')[0];
|
2020-09-09 14:37:43 +02:00
|
|
|
while (el !== null) {
|
|
|
|
if (!el.classList.contains('chat-info')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Check whether el is still from today.
|
|
|
|
// We don't use `Dayjs.same` here, since it's about 4 times slower.
|
|
|
|
const date = el.getAttribute('data-isodate');
|
|
|
|
if (date && date.split('T')[0] !== today) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const data = el?.dataset || {};
|
2020-12-06 20:42:12 +01:00
|
|
|
if (data.join === nick || data.leave === nick || data.leavejoin === nick || data.joinleave === nick) {
|
2020-09-09 14:37:43 +02:00
|
|
|
return el;
|
|
|
|
}
|
|
|
|
el = el.previousElementSibling;
|
|
|
|
}
|
|
|
|
},
|
2018-10-10 22:28:32 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Rerender the groupchat after some kind of transition. For
|
|
|
|
* example after the spinner has been removed or after a
|
|
|
|
* form has been submitted and removed.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#renderAfterTransition
|
|
|
|
*/
|
|
|
|
renderAfterTransition () {
|
2020-12-06 20:42:12 +01:00
|
|
|
const conn_status = this.model.session.get('connection_status');
|
2020-09-09 14:37:43 +02:00
|
|
|
if (conn_status == converse.ROOMSTATUS.NICKNAME_REQUIRED) {
|
|
|
|
this.renderNicknameForm();
|
|
|
|
} else if (conn_status == converse.ROOMSTATUS.PASSWORD_REQUIRED) {
|
|
|
|
this.renderPasswordForm();
|
|
|
|
} else if (conn_status == converse.ROOMSTATUS.ENTERED) {
|
|
|
|
this.hideChatRoomContents();
|
|
|
|
u.showElement(this.el.querySelector('.chat-area'));
|
2020-10-23 20:49:10 +02:00
|
|
|
this.el.querySelector('.occupants')?.setVisibility();
|
2020-09-09 14:37:43 +02:00
|
|
|
this.scrollDown();
|
|
|
|
}
|
|
|
|
},
|
2019-06-11 12:16:27 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
showSpinner () {
|
|
|
|
sizzle('.spinner', this.el).forEach(u.removeElement);
|
|
|
|
this.hideChatRoomContents();
|
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
2020-12-06 20:42:12 +01:00
|
|
|
container_el.insertAdjacentElement('afterbegin', u.getElementFromTemplateResult(tpl_spinner()));
|
2020-09-09 14:37:43 +02:00
|
|
|
},
|
2018-10-23 03:41:38 +02:00
|
|
|
|
2020-09-09 14:37:43 +02:00
|
|
|
/**
|
|
|
|
* Check if the spinner is being shown and if so, hide it.
|
|
|
|
* Also make sure then that the chat area and occupants
|
|
|
|
* list are both visible.
|
|
|
|
* @private
|
|
|
|
* @method _converse.ChatRoomView#hideSpinner
|
|
|
|
*/
|
|
|
|
hideSpinner () {
|
|
|
|
const spinner = this.el.querySelector('.spinner');
|
|
|
|
if (spinner !== null) {
|
|
|
|
u.removeElement(spinner);
|
|
|
|
this.renderAfterTransition();
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
2020-12-06 20:42:12 +01:00
|
|
|
};
|
2020-09-09 14:37:43 +02:00
|
|
|
|
2020-12-06 20:42:12 +01:00
|
|
|
export default ChatRoomViewMixin;
|