xmpp.chapril.org-conversejs/src/plugins/muc-views/bottom_panel.js

312 lines
11 KiB
JavaScript
Raw Normal View History

import BottomPanel from 'plugins/chatview/bottom_panel.js';
import debounce from 'lodash/debounce';
import tpl_muc_bottom_panel from './templates/muc-bottom-panel.js';
import { __ } from 'i18n';
import { _converse, api, converse } from "@converse/headless/core";
import { getAutoCompleteListItem } from './utils.js';
import { render } from 'lit-html';
const { Strophe, $pres } = converse.env;
const COMMAND_TO_AFFILIATION = {
'admin': 'admin',
'ban': 'outcast',
'member': 'member',
'owner': 'owner',
'revoke': 'none'
};
const COMMAND_TO_ROLE = {
'deop': 'participant',
'kick': 'none',
'mute': 'visitor',
'op': 'moderator',
'voice': 'participant'
};
const u = converse.env.utils;
export default class MUCBottomPanel extends BottomPanel {
events = {
'click .hide-occupants': 'hideOccupants',
'click .send-button': 'onFormSubmitted',
}
async connectedCallback () {
super.connectedCallback();
this.debouncedRender = debounce(this.render, 100);
this.model = _converse.chatboxes.get(this.getAttribute('jid'));
this.listenTo(this.model, 'change:composing_spoiler', this.renderMessageForm);
await this.model.initialized;
this.listenTo(this.model, 'change:hidden_occupants', this.debouncedRender);
this.listenTo(this.model.features, 'change:moderated', this.debouncedRender);
this.listenTo(this.model.occupants, 'add', this.renderIfOwnOccupant)
this.listenTo(this.model.occupants, 'change:role', this.renderIfOwnOccupant);
this.listenTo(this.model.session, 'change:connection_status', this.debouncedRender);
this.render();
}
render () {
const entered = this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED;
const can_edit = entered && !(this.model.features.get('moderated') && this.model.getOwnRole() === 'visitor');
render(tpl_muc_bottom_panel({ can_edit, entered, 'model': this.model }), this);
if (entered && can_edit) {
this.renderMessageForm();
this.initMentionAutoComplete();
}
}
renderIfOwnOccupant (o) {
(o.get('jid') === _converse.bare_jid) && this.debouncedRender();
}
getToolbarOptions () {
return Object.assign(super.getToolbarOptions(), {
'is_groupchat': true,
'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
});
}
getAutoCompleteList () {
return this.model.getAllKnownNicknames().map(nick => ({ 'label': nick, 'value': `@${nick}` }));
}
initMentionAutoComplete () {
this.mention_auto_complete = new _converse.AutoComplete(this, {
'auto_first': true,
'auto_evaluate': false,
'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', '@'],
'include_triggers': [],
'item': getAutoCompleteListItem
});
this.mention_auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false));
}
/**
* Hide the right sidebar containing the chat occupants.
* @private
* @method _converse.ChatRoomView#hideOccupants
*/
hideOccupants (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
this.model.save({ 'hidden_occupants': true });
_converse.chatboxviews.get(this.getAttribute('jid'))?.scrollDown();
}
onKeyDown (ev) {
if (this.mention_auto_complete.onKeyDown(ev)) {
return;
}
super.onKeyDown(ev);
}
onKeyUp (ev) {
this.mention_auto_complete.evaluate(ev);
super.onKeyUp(ev);
}
setRole (command, args, required_affiliations = [], required_roles = []) {
/* 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.model.verifyAffiliations(required_affiliations) || !this.model.verifyRoles(required_roles)) {
return false;
}
if (!this.model.validateRoleOrAffiliationChangeArgs(command, args)) {
return false;
}
const nick_or_jid = this.model.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.model.onCommandError.bind(this));
return true;
}
setAffiliation (command, args, required_affiliations) {
const affiliation = COMMAND_TO_AFFILIATION[command];
if (!affiliation) {
throw Error(`ChatRoomView#setAffiliation called with invalid command: ${command}`);
}
if (!this.model.verifyAffiliations(required_affiliations)) {
return false;
}
if (!this.model.validateRoleOrAffiliationChangeArgs(command, args)) {
return false;
}
const nick_or_jid = this.model.getNickOrJIDFromCommandArgs(args);
if (!nick_or_jid) {
return false;
}
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 = __(
"Couldn't find a participant with that nickname. " + 'They might have left the groupchat.'
);
this.model.createMessage({ message, 'type': 'error' });
return;
}
}
const attrs = { jid, reason };
if (occupant && api.settings.get('auto_register_muc_nickname')) {
attrs['nick'] = occupant.get('nick');
}
this.model
.setAffiliation(affiliation, [attrs])
.then(() => this.model.occupants.fetchMembers())
.catch(err => this.model.onCommandError(err));
}
parseMessageForCommands (text) {
if (
api.settings.get('muc_disable_slash_commands') &&
!Array.isArray(api.settings.get('muc_disable_slash_commands'))
) {
return super.parseMessageForCommands(text);
}
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();
if (!this.model.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': {
const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));
chatview.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.model.verifyAffiliations(['owner'])) {
break;
}
const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));
chatview.destroy().catch(e => this.model.onCommandError(e));
break;
}
case 'help': {
this.model.set({ 'show_help_messages': true });
break;
}
case 'kick': {
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.model.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'));
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()
);
}
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 => {
err_msg && this.model.createMessage({ 'message': err_msg, 'type': 'error' });
});
}
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 super.parseMessageForCommands(text);
}
return true;
}
}
api.elements.define('converse-muc-bottom-panel', MUCBottomPanel);