|
@@ -14,6 +14,7 @@ import "backbone.vdomview";
|
|
|
import BrowserStorage from "backbone.browserStorage";
|
|
|
import { OrderedListView } from "backbone.overview";
|
|
|
import _FormData from "formdata-polyfill";
|
|
|
+import bootstrap from "bootstrap.native";
|
|
|
import converse from "@converse/headless/converse-core";
|
|
|
import muc_utils from "@converse/headless/utils/muc";
|
|
|
import tpl_add_chatroom_modal from "templates/add_chatroom_modal.html";
|
|
@@ -32,6 +33,7 @@ import tpl_chatroom_password_form from "templates/chatroom_password_form.html";
|
|
|
import tpl_chatroom_sidebar from "templates/chatroom_sidebar.html";
|
|
|
import tpl_info from "templates/info.html";
|
|
|
import tpl_list_chatrooms_modal from "templates/list_chatrooms_modal.html";
|
|
|
+import tpl_moderator_tools_modal from "templates/moderator_tools_modal.html";
|
|
|
import tpl_occupant from "templates/occupant.html";
|
|
|
import tpl_room_description from "templates/room_description.html";
|
|
|
import tpl_room_item from "templates/room_item.html";
|
|
@@ -43,8 +45,12 @@ import xss from "xss/dist/xss";
|
|
|
|
|
|
const { Backbone, Promise, Strophe, dayjs, sizzle, _, $iq, $msg, $pres } = converse.env;
|
|
|
const u = converse.env.utils;
|
|
|
+
|
|
|
+const ROLES = ['moderator', 'participant', 'visitor'];
|
|
|
+const AFFILIATIONS = ['admin', 'member', 'outcast', 'owner'];
|
|
|
+const AFFILIATION_CHANGE_COMANDS = ['admin', 'ban', 'owner', 'member', 'revoke'];
|
|
|
const OWNER_COMMANDS = ['owner'];
|
|
|
-const ADMIN_COMMANDS = ['admin', 'ban', 'deop', 'destroy', 'member', 'op', 'revoke'];
|
|
|
+const ADMIN_COMMANDS = ['admin', 'ban', 'deop', 'destroy', 'modtools', 'member', 'op', 'revoke'];
|
|
|
const MODERATOR_COMMANDS = ['kick', 'mute', 'voice'];
|
|
|
const VISITOR_COMMANDS = ['nick'];
|
|
|
|
|
@@ -202,6 +208,167 @@ converse.plugins.add('converse-muc-views', {
|
|
|
}
|
|
|
|
|
|
|
|
|
+ _converse.ModeratorToolsModal = _converse.BootstrapModal.extend({
|
|
|
+
|
|
|
+ events: {
|
|
|
+ 'submit .affiliation-form': 'assignAffiliation',
|
|
|
+ 'submit .role-form': 'assignRole',
|
|
|
+ 'submit .query-affiliation': 'queryAffiliation',
|
|
|
+ 'submit .query-role': 'queryRole',
|
|
|
+ 'click .nav-item .nav-link': 'switchTab',
|
|
|
+ 'click .toggle-form': 'toggleForm',
|
|
|
+ },
|
|
|
+
|
|
|
+ initialize (attrs) {
|
|
|
+ this.chatroomview = attrs.chatroomview;
|
|
|
+ _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
|
|
|
+
|
|
|
+ this.model.on('change:role', () => {
|
|
|
+ this.users_with_role = this.getUsersWithRole();
|
|
|
+ this.render();
|
|
|
+ });
|
|
|
+ this.model.on('change:affiliation', async () => {
|
|
|
+ this.loading_users_with_affiliation = true;
|
|
|
+ this.users_with_affiliation = null;
|
|
|
+ this.render();
|
|
|
+ const affiliation = this.model.get('affiliation');
|
|
|
+ if (!_converse.muc_fetch_members || affiliation === 'outcast') {
|
|
|
+ this.users_with_affiliation = await this.chatroomview.model.getAffiliationList(affiliation);
|
|
|
+ } else {
|
|
|
+ this.users_with_affiliation = this.getUsersWithAffiliation();
|
|
|
+ }
|
|
|
+ this.loading_users_with_affiliation = false;
|
|
|
+ this.render();
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ toHTML () {
|
|
|
+ const allowed_commands = this.chatroomview.getAllowedCommands();
|
|
|
+ const allowed_affiliations = allowed_commands.map(c => COMMAND_TO_AFFILIATION[c]).filter(c => c);
|
|
|
+ const allowed_roles = _.uniq(allowed_commands
|
|
|
+ .map(c => COMMAND_TO_ROLE[c])
|
|
|
+ .filter(c => c));
|
|
|
+
|
|
|
+ allowed_affiliations.sort();
|
|
|
+ allowed_roles.sort();
|
|
|
+
|
|
|
+ return tpl_moderator_tools_modal(Object.assign(this.model.toJSON(), {
|
|
|
+ '__': __,
|
|
|
+ 'affiliations': AFFILIATIONS,
|
|
|
+ 'allowed_affiliations': allowed_affiliations,
|
|
|
+ 'allowed_roles': allowed_roles,
|
|
|
+ 'loading_users_with_affiliation': this.loading_users_with_affiliation,
|
|
|
+ 'roles': ROLES,
|
|
|
+ 'users_with_affiliation': this.users_with_affiliation,
|
|
|
+ 'users_with_role': this.users_with_role
|
|
|
+ }));
|
|
|
+ },
|
|
|
+
|
|
|
+ toggleForm (ev) {
|
|
|
+ ev.stopPropagation();
|
|
|
+ ev.preventDefault();
|
|
|
+ const form_class = ev.target.getAttribute('data-form');
|
|
|
+ const form = u.ancestor(ev.target, '.list-group-item').querySelector(`.${form_class}`);
|
|
|
+ if (u.hasClass('hidden', form)) {
|
|
|
+ u.removeClass('hidden', form);
|
|
|
+ } else {
|
|
|
+ u.addClass('hidden', form);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ getUsersWithAffiliation () {
|
|
|
+ return this.chatroomview.model.occupants
|
|
|
+ .where({'affiliation': this.model.get('affiliation')})
|
|
|
+ .map(item => {
|
|
|
+ return {
|
|
|
+ 'jid': item.get('jid'),
|
|
|
+ 'nick': item.get('nick'),
|
|
|
+ 'affiliation': item.get('affiliation')
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ getUsersWithRole () {
|
|
|
+ return this.chatroomview.model.occupants
|
|
|
+ .where({'role': this.model.get('role')})
|
|
|
+ .map(item => {
|
|
|
+ return {
|
|
|
+ 'jid': item.get('jid'),
|
|
|
+ 'nick': item.get('nick'),
|
|
|
+ 'role': item.get('role')
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ queryRole (ev) {
|
|
|
+ ev.stopPropagation();
|
|
|
+ ev.preventDefault();
|
|
|
+ const data = new FormData(ev.target);
|
|
|
+ const role = data.get('role');
|
|
|
+ this.model.set({'role': null}, {'silent': true});
|
|
|
+ this.model.set({'role': role});
|
|
|
+ },
|
|
|
+
|
|
|
+ queryAffiliation (ev) {
|
|
|
+ ev.stopPropagation();
|
|
|
+ ev.preventDefault();
|
|
|
+ const data = new FormData(ev.target);
|
|
|
+ const affiliation = data.get('affiliation');
|
|
|
+ this.model.set({'affiliation': null}, {'silent': true});
|
|
|
+ this.model.set({'affiliation': affiliation});
|
|
|
+ },
|
|
|
+
|
|
|
+ assignAffiliation (ev) {
|
|
|
+ ev.stopPropagation();
|
|
|
+ ev.preventDefault();
|
|
|
+ const data = new FormData(ev.target);
|
|
|
+ const affiliation = data.get('affiliation');
|
|
|
+ const attrs = {
|
|
|
+ 'jid': data.get('jid'),
|
|
|
+ 'reason': data.get('reason')
|
|
|
+ }
|
|
|
+ const current_affiliation = this.model.get('affiliation');
|
|
|
+ this.chatroomview.model.setAffiliation(affiliation, [attrs])
|
|
|
+ .then(async () => {
|
|
|
+ this.alert(__('Affiliation changed'), 'primary');
|
|
|
+ await this.chatroomview.model.occupants.fetchMembers()
|
|
|
+ this.model.set({'affiliation': null}, {'silent': true});
|
|
|
+ this.model.set({'affiliation': current_affiliation});
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ this.alert(__('Sorry, something went wrong while trying to set the affiliation'), 'danger');
|
|
|
+ _converse.log(err, Strophe.LogLevel.ERROR);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ assignRole (ev) {
|
|
|
+ ev.stopPropagation();
|
|
|
+ ev.preventDefault();
|
|
|
+ const data = new FormData(ev.target);
|
|
|
+ const jid = data.get('jid');
|
|
|
+ const occupant = this.chatroomview.model.getOccupant(jid);
|
|
|
+ const role = data.get('role');
|
|
|
+ const reason = data.get('reason');
|
|
|
+ const current_role = this.model.get('role');
|
|
|
+ this.chatroomview.model.setRole(occupant, role, reason,
|
|
|
+ () => {
|
|
|
+ this.alert(__('Role changed'), 'primary');
|
|
|
+ this.model.set({'role': null}, {'silent': true});
|
|
|
+ this.model.set({'role': current_role});
|
|
|
+ },
|
|
|
+ (e) => {
|
|
|
+ if (sizzle(`not-allowed[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
|
|
|
+ this.alert(__('You\'re not allowed to make that change'), 'danger');
|
|
|
+ } else {
|
|
|
+ this.alert(__('Sorry, something went wrong while trying to set the role'), 'danger');
|
|
|
+ }
|
|
|
+ _converse.log(e, Strophe.LogLevel.ERROR);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
_converse.ListChatRoomsModal = _converse.BootstrapModal.extend({
|
|
|
|
|
|
events: {
|
|
@@ -432,7 +599,6 @@ converse.plugins.add('converse-muc-views', {
|
|
|
|
|
|
/**
|
|
|
* The View of an open/ongoing groupchat conversation
|
|
|
- *
|
|
|
* @class
|
|
|
* @namespace _converse.ChatRoomView
|
|
|
* @memberOf _converse
|
|
@@ -594,6 +760,16 @@ converse.plugins.add('converse-muc-views', {
|
|
|
return _converse.ChatBoxView.prototype.onKeyUp.call(this, ev);
|
|
|
},
|
|
|
|
|
|
+ showModeratorToolsModal (affiliation) {
|
|
|
+ if (_.isUndefined(this.model.modtools_modal)) {
|
|
|
+ const model = new Backbone.Model({'affiliation': affiliation});
|
|
|
+ this.modtools_modal = new _converse.ModeratorToolsModal({'model': model, 'chatroomview': this});
|
|
|
+ } else {
|
|
|
+ this.modtools_modal.set('affiliation', affiliation);
|
|
|
+ }
|
|
|
+ this.modtools_modal.show();
|
|
|
+ },
|
|
|
+
|
|
|
showRoomDetailsModal (ev) {
|
|
|
ev.preventDefault();
|
|
|
if (this.model.room_details_modal === undefined) {
|
|
@@ -927,6 +1103,26 @@ converse.plugins.add('converse-muc-views', {
|
|
|
this.showErrorMessage(__("Sorry, an error happened while running the command. Check your browser's developer console for details."));
|
|
|
},
|
|
|
|
|
|
+ getAllowedCommands () {
|
|
|
+ // FIXME: The availability of some of these commands
|
|
|
+ // depend on the MUCs configuration (e.g. whether it's
|
|
|
+ // moderated or not). We need to take that into
|
|
|
+ // consideration.
|
|
|
+ let allowed_commands = ['clear', 'help', 'me', 'nick', 'subject', 'topic', 'register'];
|
|
|
+ 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)) {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ return allowed_commands;
|
|
|
+ },
|
|
|
+
|
|
|
parseMessageForCommands (text) {
|
|
|
if (_converse.muc_disable_slash_commands && !Array.isArray(_converse.muc_disable_slash_commands)) {
|
|
|
return _converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments);
|
|
@@ -936,7 +1132,7 @@ converse.plugins.add('converse-muc-views', {
|
|
|
if (!command) {
|
|
|
return false;
|
|
|
}
|
|
|
- const args = text.slice(('/'+command).length+1);
|
|
|
+ const args = text.slice(('/'+command).length+1).trim();
|
|
|
|
|
|
let disabled_commands = [];
|
|
|
if (Array.isArray(_converse.muc_disable_slash_commands)) {
|
|
@@ -955,6 +1151,10 @@ converse.plugins.add('converse-muc-views', {
|
|
|
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
|
|
@@ -975,22 +1175,7 @@ converse.plugins.add('converse-muc-views', {
|
|
|
break;
|
|
|
}
|
|
|
case 'help': {
|
|
|
- // FIXME: The availability of some of these commands
|
|
|
- // depend on the MUCs configuration (e.g. whether it's
|
|
|
- // moderated or not). We need to take that into
|
|
|
- // consideration.
|
|
|
- let allowed_commands = ['clear', 'help', 'me', 'nick', 'subject', 'topic', 'register'];
|
|
|
- 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)) {
|
|
|
- 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);
|
|
|
- }
|
|
|
+ const allowed_commands = this.getAllowedCommands();
|
|
|
this.showHelpMessages([`<strong>${__("You can run the following commands")}</strong>`]);
|
|
|
this.showHelpMessages([
|
|
|
`<strong>/admin</strong>: ${__("Change user's affiliation to admin")}`,
|
|
@@ -1002,6 +1187,7 @@ converse.plugins.add('converse-muc-views', {
|
|
|
`<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')}`,
|