Extract moderator tools functionality and put it in a component
This makes it easier for 3rd parties to embed it in other modals (besides the bootstrap modal).
This commit is contained in:
parent
10c610232c
commit
0242fdb020
|
@ -2,10 +2,11 @@
|
|||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import { AFFILIATIONS } from '@converse/headless/plugins/muc/index.js';
|
||||
import difference from 'lodash-es/difference';
|
||||
import indexOf from 'lodash-es/indexOf';
|
||||
import log from "@converse/headless/log";
|
||||
import { api, converse } from '@converse/headless/core.js';
|
||||
import { _converse, api, converse } from '@converse/headless/core.js';
|
||||
import { parseMemberListIQ } from '../parsers.js';
|
||||
|
||||
const { Strophe, $iq, u } = converse.env;
|
||||
|
@ -20,19 +21,20 @@ const { Strophe, $iq, u } = converse.env;
|
|||
* @returns { Promise<MemberListItem[]> }
|
||||
*/
|
||||
export async function getAffiliationList (affiliation, muc_jid) {
|
||||
const { __ } = _converse;
|
||||
const iq = $iq({ 'to': muc_jid, 'type': 'get' })
|
||||
.c('query', { xmlns: Strophe.NS.MUC_ADMIN })
|
||||
.c('item', { 'affiliation': affiliation });
|
||||
const result = await api.sendIQ(iq, null, false);
|
||||
if (result === null) {
|
||||
const err_msg = `Error: timeout while fetching ${affiliation} list for MUC ${muc_jid}`;
|
||||
const err_msg = __('Error: timeout while fetching %1s list for MUC %2s', affiliation, muc_jid);
|
||||
const err = new Error(err_msg);
|
||||
log.warn(err_msg);
|
||||
log.warn(result);
|
||||
return err;
|
||||
}
|
||||
if (u.isErrorStanza(result)) {
|
||||
const err_msg = `Error: not allowed to fetch ${affiliation} list for MUC ${muc_jid}`;
|
||||
const err_msg = __('Error: not allowed to fetch %1s list for MUC %2s', affiliation, muc_jid);
|
||||
const err = new Error(err_msg);
|
||||
log.warn(err_msg);
|
||||
log.warn(result);
|
||||
|
@ -43,6 +45,28 @@ export async function getAffiliationList (affiliation, muc_jid) {
|
|||
.sort((a, b) => (a.nick < b.nick ? -1 : a.nick > b.nick ? 1 : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an occupant model, see which affiliations may be assigned to that user.
|
||||
* @param { Model } occupant
|
||||
* @returns { ('owner', 'admin', 'member', 'outcast', 'none')[] } - An array of assignable affiliations
|
||||
*/
|
||||
export function getAssignableAffiliations (occupant) {
|
||||
let disabled = api.settings.get('modtools_disable_assign');
|
||||
if (!Array.isArray(disabled)) {
|
||||
disabled = disabled ? AFFILIATIONS : [];
|
||||
}
|
||||
if (occupant.get('affiliation') === 'owner') {
|
||||
return AFFILIATIONS.filter(a => !disabled.includes(a));
|
||||
} else if (occupant.get('affiliation') === 'admin') {
|
||||
return AFFILIATIONS.filter(a => !['owner', 'admin', ...disabled].includes(a));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Necessary for tests
|
||||
_converse.getAssignableAffiliations = getAssignableAffiliations;
|
||||
|
||||
/**
|
||||
* Send IQ stanzas to the server to modify affiliations for users in this groupchat.
|
||||
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
||||
|
|
|
@ -236,6 +236,7 @@ converse.plugins.add('converse-muc', {
|
|||
'auto_register_muc_nickname': false,
|
||||
'hide_muc_participants': false,
|
||||
'locked_muc_domain': false,
|
||||
'modtools_disable_assign': false,
|
||||
'muc_clear_messages_on_leave': true,
|
||||
'muc_domain': undefined,
|
||||
'muc_fetch_members': true,
|
||||
|
|
21
src/headless/plugins/muc/utils.js
Normal file
21
src/headless/plugins/muc/utils.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { ROLES } from '@converse/headless/plugins/muc/index.js';
|
||||
import { _converse, api } from '@converse/headless/core.js';
|
||||
|
||||
/**
|
||||
* Given an occupant model, see which roles may be assigned to that user.
|
||||
* @param { Model } occupant
|
||||
* @returns { ('moderator', 'participant', 'visitor')[] } - An array of assignable roles
|
||||
*/
|
||||
export function getAssignableRoles (occupant) {
|
||||
let disabled = api.settings.get('modtools_disable_assign');
|
||||
if (!Array.isArray(disabled)) {
|
||||
disabled = disabled ? ROLES : [];
|
||||
}
|
||||
if (occupant.get('role') === 'moderator') {
|
||||
return ROLES.filter(r => !disabled.includes(r));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(_converse, { getAssignableRoles });
|
|
@ -4,7 +4,7 @@ import { CustomElement } from 'shared/components/element.js';
|
|||
import { __ } from 'i18n';
|
||||
import { _converse, api } from "@converse/headless/core";
|
||||
|
||||
import './styles//chat-head.scss';
|
||||
import './styles/chat-head.scss';
|
||||
|
||||
|
||||
export default class ChatHeading extends CustomElement {
|
||||
|
|
|
@ -44,7 +44,6 @@ converse.plugins.add('converse-muc-views', {
|
|||
'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,
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import '../modtools.js';
|
||||
import BootstrapModal from "modals/base.js";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_moderator_tools_modal from "../templates/moderator-tools.js";
|
||||
import { AFFILIATIONS, ROLES } from "@converse/headless/plugins/muc/index.js";
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
import { getAffiliationList, setAffiliation } from '@converse/headless/plugins/muc/affiliations/utils.js'
|
||||
|
||||
const { Strophe, sizzle } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
import tpl_moderator_tools from './templates/moderator-tools.js';
|
||||
|
||||
const ModeratorToolsModal = BootstrapModal.extend({
|
||||
id: "converse-modtools-modal",
|
||||
|
@ -17,190 +9,10 @@ const ModeratorToolsModal = BootstrapModal.extend({
|
|||
initialize (attrs) {
|
||||
this.muc = attrs.muc;
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.affiliations_filter = '';
|
||||
this.roles_filter = '';
|
||||
|
||||
this.listenTo(this.model, 'change:role', () => {
|
||||
this.users_with_role = this.muc.getOccupantsWithRole(this.model.get('role'));
|
||||
this.render();
|
||||
});
|
||||
this.listenTo(this.model, 'change:affiliation', async () => {
|
||||
this.loading_users_with_affiliation = true;
|
||||
this.users_with_affiliation = null;
|
||||
this.render();
|
||||
const affiliation = this.model.get('affiliation');
|
||||
if (this.shouldFetchAffiliationsList()) {
|
||||
const muc_jid = this.muc.get('jid');
|
||||
this.users_with_affiliation = await getAffiliationList(affiliation, muc_jid);
|
||||
} else {
|
||||
this.users_with_affiliation = this.muc.getOccupantsWithAffiliation(affiliation);
|
||||
}
|
||||
this.loading_users_with_affiliation = false;
|
||||
this.render();
|
||||
});
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
const occupant = this.muc.occupants.findWhere({'jid': _converse.bare_jid});
|
||||
return tpl_moderator_tools_modal(Object.assign(this.model.toJSON(), {
|
||||
'affiliations_filter': this.affiliations_filter,
|
||||
'assignAffiliation': ev => this.assignAffiliation(ev),
|
||||
'assignRole': ev => this.assignRole(ev),
|
||||
'assignable_affiliations': this.getAssignableAffiliations(occupant),
|
||||
'assignable_roles': this.getAssignableRoles(occupant),
|
||||
'filterAffiliationResults': ev => this.filterAffiliationResults(ev),
|
||||
'filterRoleResults': ev => this.filterRoleResults(ev),
|
||||
'loading_users_with_affiliation': this.loading_users_with_affiliation,
|
||||
'queryAffiliation': ev => this.queryAffiliation(ev),
|
||||
'queryRole': ev => this.queryRole(ev),
|
||||
'queryable_affiliations': AFFILIATIONS.filter(a => !_converse.modtools_disable_query.includes(a)),
|
||||
'queryable_roles': ROLES.filter(a => !_converse.modtools_disable_query.includes(a)),
|
||||
'roles_filter': this.roles_filter,
|
||||
'switchTab': ev => this.switchTab(ev),
|
||||
'toggleForm': ev => this.toggleForm(ev),
|
||||
'users_with_affiliation': this.users_with_affiliation,
|
||||
'users_with_role': this.users_with_role
|
||||
}));
|
||||
},
|
||||
|
||||
getAssignableAffiliations (occupant) {
|
||||
let disabled = api.settings.get('modtools_disable_assign');
|
||||
if (!Array.isArray(disabled)) {
|
||||
disabled = disabled ? AFFILIATIONS : [];
|
||||
}
|
||||
|
||||
if (occupant.get('affiliation') === 'owner') {
|
||||
return AFFILIATIONS.filter(a => !disabled.includes(a));
|
||||
} else if (occupant.get('affiliation') === 'admin') {
|
||||
return AFFILIATIONS.filter(a => !['owner', 'admin', ...disabled].includes(a));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
getAssignableRoles (occupant) {
|
||||
let disabled = api.settings.get('modtools_disable_assign');
|
||||
if (!Array.isArray(disabled)) {
|
||||
disabled = disabled ? ROLES : [];
|
||||
}
|
||||
|
||||
if (occupant.get('role') === 'moderator') {
|
||||
return ROLES.filter(r => !disabled.includes(r));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
shouldFetchAffiliationsList () {
|
||||
const affiliation = this.model.get('affiliation');
|
||||
if (affiliation === 'none') {
|
||||
return false;
|
||||
}
|
||||
const chatroom = this.muc;
|
||||
const auto_fetched_affs = chatroom.occupants.getAutoFetchedAffiliationLists();
|
||||
if (auto_fetched_affs.includes(affiliation)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
filterRoleResults (ev) {
|
||||
this.roles_filter = ev.target.value;
|
||||
this.render();
|
||||
},
|
||||
|
||||
filterAffiliationResults (ev) {
|
||||
this.affiliations_filter = ev.target.value;
|
||||
this.render();
|
||||
},
|
||||
|
||||
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});
|
||||
},
|
||||
|
||||
async 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');
|
||||
const muc_jid = this.muc.get('jid');
|
||||
try {
|
||||
await setAffiliation(affiliation, muc_jid, [attrs]);
|
||||
} catch (e) {
|
||||
if (e === null) {
|
||||
this.alert(__('Timeout error while trying to set the affiliation'), 'danger');
|
||||
} else if (sizzle(`not-allowed[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
|
||||
this.alert(__('Sorry, you\'re not allowed to make that change'), 'danger');
|
||||
} else {
|
||||
this.alert(__('Sorry, something went wrong while trying to set the affiliation'), 'danger');
|
||||
}
|
||||
log.error(e);
|
||||
return;
|
||||
}
|
||||
this.alert(__('Affiliation changed'), 'primary');
|
||||
await this.muc.occupants.fetchMembers()
|
||||
this.model.set({'affiliation': null}, {'silent': true});
|
||||
this.model.set({'affiliation': current_affiliation});
|
||||
},
|
||||
|
||||
assignRole (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target);
|
||||
const occupant = this.muc.getOccupant(data.get('jid') || data.get('nick'));
|
||||
const role = data.get('role');
|
||||
const reason = data.get('reason');
|
||||
const current_role = this.model.get('role');
|
||||
this.muc.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');
|
||||
if (u.isErrorObject(e)) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return tpl_moderator_tools(this);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
19
src/plugins/muc-views/modals/templates/moderator-tools.js
Normal file
19
src/plugins/muc-views/modals/templates/moderator-tools.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "modals/templates/buttons.js"
|
||||
|
||||
export default (o) => {
|
||||
const i18n_moderator_tools = __('Moderator Tools');
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="converse-modtools-modal-label">${i18n_moderator_tools}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<converse-modtools .muc=${o.muc} .model=${o.model}></converse-modtools>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
225
src/plugins/muc-views/modtools.js
Normal file
225
src/plugins/muc-views/modtools.js
Normal file
|
@ -0,0 +1,225 @@
|
|||
import log from '@converse/headless/log';
|
||||
import tpl_moderator_tools from './templates/moderator-tools.js';
|
||||
import { AFFILIATIONS, ROLES } from '@converse/headless/plugins/muc/index.js';
|
||||
import { CustomElement } from 'shared/components/element.js';
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
import { getAssignableRoles } from '@converse/headless/plugins/muc/utils.js';
|
||||
import {
|
||||
getAffiliationList,
|
||||
getAssignableAffiliations,
|
||||
setAffiliation,
|
||||
} from '@converse/headless/plugins/muc/affiliations/utils.js';
|
||||
|
||||
const { Strophe, sizzle, u } = converse.env;
|
||||
|
||||
export default class ModeratorTools extends CustomElement {
|
||||
static get properties () {
|
||||
return {
|
||||
affiliations_filter: { type: String, attribute: false },
|
||||
alert_message: { type: String, attribute: false },
|
||||
alert_type: { type: String, attribute: false },
|
||||
model: { type: Object },
|
||||
muc: { type: Object },
|
||||
roles_filter: { type: String, attribute: false },
|
||||
users_with_affiliation: { type: Array, attribute: false },
|
||||
users_with_role: { type: Array, attribute: false },
|
||||
};
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
this.affiliations_filter = '';
|
||||
this.roles_filter = '';
|
||||
}
|
||||
|
||||
connectedCallback () {
|
||||
super.connectedCallback();
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
initialize () {
|
||||
this.listenTo(this.model, 'change:role', this.onSearchRoleChange, this);
|
||||
this.listenTo(this.model, 'change:affiliation', this.onSearchAffiliationChange, this);
|
||||
}
|
||||
|
||||
render () {
|
||||
const occupant = this.muc.occupants.findWhere({ 'jid': _converse.bare_jid });
|
||||
return tpl_moderator_tools(
|
||||
Object.assign(this.model.toJSON(), {
|
||||
'affiliations_filter': this.affiliations_filter,
|
||||
'alert_message': this.alert_message,
|
||||
'alert_type': this.alert_type,
|
||||
'assignAffiliation': ev => this.assignAffiliation(ev),
|
||||
'assignRole': ev => this.assignRole(ev),
|
||||
'assignable_affiliations': getAssignableAffiliations(occupant),
|
||||
'assignable_roles': getAssignableRoles(occupant),
|
||||
'filterAffiliationResults': ev => this.filterAffiliationResults(ev),
|
||||
'filterRoleResults': ev => this.filterRoleResults(ev),
|
||||
'loading_users_with_affiliation': this.loading_users_with_affiliation,
|
||||
'queryAffiliation': ev => this.queryAffiliation(ev),
|
||||
'queryRole': ev => this.queryRole(ev),
|
||||
'queryable_affiliations': AFFILIATIONS.filter(a => !api.settings.get('modtools_disable_query').includes(a)),
|
||||
'queryable_roles': ROLES.filter(a => !api.settings.get('modtools_disable_query').includes(a)),
|
||||
'roles_filter': this.roles_filter,
|
||||
'switchTab': ev => this.switchTab(ev),
|
||||
'toggleForm': ev => this.toggleForm(ev),
|
||||
'users_with_affiliation': this.users_with_affiliation,
|
||||
'users_with_role': this.users_with_role,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async onSearchAffiliationChange () {
|
||||
this.clearAlert();
|
||||
this.loading_users_with_affiliation = true;
|
||||
this.users_with_affiliation = null;
|
||||
|
||||
const affiliation = this.model.get('affiliation');
|
||||
if (this.shouldFetchAffiliationsList()) {
|
||||
const muc_jid = this.muc.get('jid');
|
||||
const result = await getAffiliationList(affiliation, muc_jid);
|
||||
if (result instanceof Error) {
|
||||
this.alert(result.message, 'danger');
|
||||
this.users_with_affiliation = [];
|
||||
} else {
|
||||
this.users_with_affiliation = result;
|
||||
}
|
||||
} else {
|
||||
this.users_with_affiliation = this.muc.getOccupantsWithAffiliation(affiliation);
|
||||
}
|
||||
this.loading_users_with_affiliation = false;
|
||||
}
|
||||
|
||||
onSearchRoleChange () {
|
||||
this.clearAlert();
|
||||
this.users_with_role = this.muc.getOccupantsWithRole(this.model.get('role'));
|
||||
}
|
||||
|
||||
shouldFetchAffiliationsList () {
|
||||
const affiliation = this.model.get('affiliation');
|
||||
if (affiliation === 'none') {
|
||||
return false;
|
||||
}
|
||||
const chatroom = this.muc;
|
||||
const auto_fetched_affs = chatroom.occupants.getAutoFetchedAffiliationLists();
|
||||
if (auto_fetched_affs.includes(affiliation)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
toggleForm (ev) { // eslint-disable-line class-methods-use-this
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
filterRoleResults (ev) {
|
||||
this.roles_filter = ev.target.value;
|
||||
this.render();
|
||||
}
|
||||
|
||||
filterAffiliationResults (ev) {
|
||||
this.affiliations_filter = ev.target.value;
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
alert (message, type) {
|
||||
this.alert_message = message;
|
||||
this.alert_type = type;
|
||||
}
|
||||
|
||||
clearAlert () {
|
||||
this.alert_message = undefined;
|
||||
this.alert_type = undefined;
|
||||
}
|
||||
|
||||
async assignAffiliation (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.clearAlert();
|
||||
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');
|
||||
const muc_jid = this.muc.get('jid');
|
||||
try {
|
||||
await setAffiliation(affiliation, muc_jid, [attrs]);
|
||||
} catch (e) {
|
||||
if (e === null) {
|
||||
this.alert(__('Timeout error while trying to set the affiliation'), 'danger');
|
||||
} else if (sizzle(`not-allowed[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
|
||||
this.alert(__("Sorry, you're not allowed to make that change"), 'danger');
|
||||
} else {
|
||||
this.alert(__('Sorry, something went wrong while trying to set the affiliation'), 'danger');
|
||||
}
|
||||
log.error(e);
|
||||
return;
|
||||
}
|
||||
await this.muc.occupants.fetchMembers();
|
||||
this.model.set({ 'affiliation': null }, { 'silent': true });
|
||||
this.model.set({ 'affiliation': current_affiliation });
|
||||
this.alert(__('Affiliation changed'), 'primary');
|
||||
}
|
||||
|
||||
assignRole (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.clearAlert();
|
||||
const data = new FormData(ev.target);
|
||||
const occupant = this.muc.getOccupant(data.get('jid') || data.get('nick'));
|
||||
const role = data.get('role');
|
||||
const reason = data.get('reason');
|
||||
const current_role = this.model.get('role');
|
||||
this.muc.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');
|
||||
if (u.isErrorObject(e)) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-modtools', ModeratorTools);
|
|
@ -1,7 +1,6 @@
|
|||
import spinner from "templates/spinner.js";
|
||||
import { __ } from 'i18n';
|
||||
import { html } from "lit";
|
||||
import { modal_header_close_button } from "modals/templates/buttons.js"
|
||||
|
||||
|
||||
function getRoleHelpText (role) {
|
||||
|
@ -135,13 +134,13 @@ const affiliation_list_item = (o) => html`
|
|||
`;
|
||||
|
||||
|
||||
const tpl_navigation = (o) => html`
|
||||
const tpl_navigation = () => html`
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link active" id="affiliations-tab" href="#affiliations-tabpanel" aria-controls="affiliations-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>Affiliations</a>
|
||||
<a class="nav-link active" id="affiliations-tab" href="#affiliations-tabpanel" aria-controls="affiliations-tabpanel" role="tab" data-toggle="tab">Affiliations</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link" id="roles-tab" href="#roles-tabpanel" aria-controls="roles-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>Roles</a>
|
||||
<a class="nav-link" id="roles-tab" href="#roles-tabpanel" aria-controls="roles-tabpanel" role="tab" data-toggle="tab">Roles</a>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
|
@ -149,7 +148,6 @@ const tpl_navigation = (o) => html`
|
|||
|
||||
export default (o) => {
|
||||
const i18n_affiliation = __('Affiliation');
|
||||
const i18n_moderator_tools = __('Moderator Tools');
|
||||
const i18n_no_users_with_aff = __('No users with that affiliation found.')
|
||||
const i18n_no_users_with_role = __('No users with that role found.');
|
||||
const i18n_filter = __('Type here to filter the search results');
|
||||
|
@ -167,94 +165,83 @@ export default (o) => {
|
|||
);
|
||||
const show_both_tabs = o.queryable_roles.length && o.queryable_affiliations.length;
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="converse-modtools-modal-label">${i18n_moderator_tools}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<span class="modal-alert"></span>
|
||||
${o.alert_message ? html`<div class="alert alert-${o.alert_type}" role="alert">${o.alert_message}</div>` : '' }
|
||||
${ show_both_tabs ? tpl_navigation() : '' }
|
||||
|
||||
${ show_both_tabs ? tpl_navigation(o) : '' }
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane tab-pane--columns ${ o.queryable_affiliations.length ? 'active' : ''}" id="affiliations-tabpanel" role="tabpanel" aria-labelledby="affiliations-tab">
|
||||
<form class="converse-form query-affiliation" @submit=${o.queryAffiliation}>
|
||||
<p class="helptext pb-3">${i18n_helptext_affiliation}</p>
|
||||
<div class="form-group">
|
||||
<label for="affiliation">
|
||||
<strong>${i18n_affiliation}:</strong>
|
||||
</label>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<select class="custom-select select-affiliation" name="affiliation">
|
||||
${o.queryable_affiliations.map(item => affiliation_option(Object.assign({item}, o)))}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="submit" class="btn btn-primary" name="users_with_affiliation" value="${i18n_show_users}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mt-3">
|
||||
${ (Array.isArray(o.users_with_affiliation) && o.users_with_affiliation.length > 5) ?
|
||||
html`<input class="form-control" .value="${o.affiliations_filter}" @keyup=${o.filterAffiliationResults} type="text" name="filter" placeholder="${i18n_filter}"/>` : '' }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${ getAffiliationHelpText(o.affiliation) ?
|
||||
html`<div class="row"><div class="col pt-2"><p class="helptext pb-3">${getAffiliationHelpText(o.affiliation)}</p></div></div>` : '' }
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane tab-pane--columns ${ o.queryable_affiliations.length ? 'active' : ''}" id="affiliations-tabpanel" role="tabpanel" aria-labelledby="affiliations-tab">
|
||||
<form class="converse-form query-affiliation" @submit=${o.queryAffiliation}>
|
||||
<p class="helptext pb-3">${i18n_helptext_affiliation}</p>
|
||||
<div class="form-group">
|
||||
<label for="affiliation">
|
||||
<strong>${i18n_affiliation}:</strong>
|
||||
</label>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<select class="custom-select select-affiliation" name="affiliation">
|
||||
${o.queryable_affiliations.map(item => affiliation_option(Object.assign({item}, o)))}
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
<div class="scrollable-container">
|
||||
<ul class="list-group list-group--users">
|
||||
${ (o.loading_users_with_affiliation) ? html`<li class="list-group-item"> ${spinner()} </li>` : '' }
|
||||
${ (Array.isArray(o.users_with_affiliation) && o.users_with_affiliation.length === 0) ?
|
||||
html`<li class="list-group-item">${i18n_no_users_with_aff}</li>` : '' }
|
||||
|
||||
${ (o.users_with_affiliation instanceof Error) ?
|
||||
html`<li class="list-group-item">${o.users_with_affiliation.message}</li>` :
|
||||
(o.users_with_affiliation || []).map(item => ((item.nick || item.jid).match(new RegExp(o.affiliations_filter, 'i')) ? affiliation_list_item(Object.assign({item}, o)) : '')) }
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane tab-pane--columns ${ !show_both_tabs && o.queryable_roles.length ? 'active' : ''}" id="roles-tabpanel" role="tabpanel" aria-labelledby="roles-tab">
|
||||
<form class="converse-form query-role" @submit=${o.queryRole}>
|
||||
<p class="helptext pb-3">${i18n_helptext_role}</p>
|
||||
<div class="form-group">
|
||||
<label for="role"><strong>${i18n_role}:</strong></label>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<select class="custom-select select-role" name="role">
|
||||
${o.queryable_roles.map(item => role_option(Object.assign({item}, o)))}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="submit" class="btn btn-primary" name="users_with_role" value="${i18n_show_users}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mt-3">
|
||||
${ (Array.isArray(o.users_with_role) && o.users_with_role.length > 5) ?
|
||||
html`<input class="form-control" .value="${o.roles_filter}" @keyup=${o.filterRoleResults} type="text" name="filter" placeholder="${i18n_filter}"/>` : '' }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${ getRoleHelpText(o.role) ? html`<div class="row"><div class="col pt-2"><p class="helptext pb-3">${getRoleHelpText(o.role)}</p></div></div>` : ''}
|
||||
<div class="col">
|
||||
<input type="submit" class="btn btn-primary" name="users_with_affiliation" value="${i18n_show_users}"/>
|
||||
</div>
|
||||
</form>
|
||||
<div class="scrollable-container">
|
||||
<ul class="list-group list-group--users">
|
||||
${ o.loading_users_with_role ? html`<li class="list-group-item"> ${spinner()} </li>` : '' }
|
||||
${ (o.users_with_role && o.users_with_role.length === 0) ? html`<li class="list-group-item">${i18n_no_users_with_role}</li>` : '' }
|
||||
${ (o.users_with_role || []).map(item => (item.nick.match(o.roles_filter) ? role_list_item(Object.assign({item}, o)) : '')) }
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mt-3">
|
||||
${ (Array.isArray(o.users_with_affiliation) && o.users_with_affiliation.length > 5) ?
|
||||
html`<input class="form-control" .value="${o.affiliations_filter}" @keyup=${o.filterAffiliationResults} type="text" name="filter" placeholder="${i18n_filter}"/>` : '' }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${ getAffiliationHelpText(o.affiliation) ?
|
||||
html`<div class="row"><div class="col pt-2"><p class="helptext pb-3">${getAffiliationHelpText(o.affiliation)}</p></div></div>` : '' }
|
||||
</div>
|
||||
</form>
|
||||
<div class="scrollable-container">
|
||||
<ul class="list-group list-group--users">
|
||||
${ (o.loading_users_with_affiliation) ? html`<li class="list-group-item"> ${spinner()} </li>` : '' }
|
||||
${ (Array.isArray(o.users_with_affiliation) && o.users_with_affiliation.length === 0) ?
|
||||
html`<li class="list-group-item">${i18n_no_users_with_aff}</li>` : '' }
|
||||
|
||||
${ (o.users_with_affiliation instanceof Error) ?
|
||||
html`<li class="list-group-item">${o.users_with_affiliation.message}</li>` :
|
||||
(o.users_with_affiliation || []).map(item => ((item.nick || item.jid).match(new RegExp(o.affiliations_filter, 'i')) ? affiliation_list_item(Object.assign({item}, o)) : '')) }
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
<div class="tab-pane tab-pane--columns ${ !show_both_tabs && o.queryable_roles.length ? 'active' : ''}" id="roles-tabpanel" role="tabpanel" aria-labelledby="roles-tab">
|
||||
<form class="converse-form query-role" @submit=${o.queryRole}>
|
||||
<p class="helptext pb-3">${i18n_helptext_role}</p>
|
||||
<div class="form-group">
|
||||
<label for="role"><strong>${i18n_role}:</strong></label>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<select class="custom-select select-role" name="role">
|
||||
${o.queryable_roles.map(item => role_option(Object.assign({item}, o)))}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="submit" class="btn btn-primary" name="users_with_role" value="${i18n_show_users}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mt-3">
|
||||
${ (Array.isArray(o.users_with_role) && o.users_with_role.length > 5) ?
|
||||
html`<input class="form-control" .value="${o.roles_filter}" @keyup=${o.filterRoleResults} type="text" name="filter" placeholder="${i18n_filter}"/>` : '' }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${ getRoleHelpText(o.role) ? html`<div class="row"><div class="col pt-2"><p class="helptext pb-3">${getRoleHelpText(o.role)}</p></div></div>` : ''}
|
||||
</div>
|
||||
</form>
|
||||
<div class="scrollable-container">
|
||||
<ul class="list-group list-group--users">
|
||||
${ o.loading_users_with_role ? html`<li class="list-group-item"> ${spinner()} </li>` : '' }
|
||||
${ (o.users_with_role && o.users_with_role.length === 0) ? html`<li class="list-group-item">${i18n_no_users_with_role}</li>` : '' }
|
||||
${ (o.users_with_role || []).map(item => (item.nick.match(o.roles_filter) ? role_list_item(Object.assign({item}, o)) : '')) }
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
|
|
@ -338,9 +338,12 @@ describe("The groupchat moderator tool", function () {
|
|||
_converse.connection._dataRecv(mock.createRequest(error));
|
||||
await u.waitUntil(() => !modal.loading_users_with_affiliation);
|
||||
|
||||
const alert = await u.waitUntil(() => modal.el.querySelector('.alert'));
|
||||
expect(alert.textContent.trim()).toBe('Error: not allowed to fetch outcast list for MUC lounge@montague.lit');
|
||||
|
||||
const user_els = modal.el.querySelectorAll('.list-group--users > li');
|
||||
expect(user_els.length).toBe(1);
|
||||
expect(user_els[0].textContent.trim()).toBe('Error: not allowed to fetch outcast list for MUC lounge@montague.lit');
|
||||
expect(user_els[0].textContent.trim()).toBe('No users with that affiliation found.');
|
||||
}));
|
||||
|
||||
it("shows an error message if a particular affiliation may not be set",
|
||||
|
@ -452,23 +455,23 @@ describe("The groupchat moderator tool", function () {
|
|||
const message_form = view.querySelector('converse-muc-message-form');
|
||||
message_form.onKeyDown(enter);
|
||||
|
||||
const modal = await u.waitUntil(() => _converse.api.modal.get('converse-modtools-modal'));
|
||||
await u.waitUntil(() => _converse.api.modal.get('converse-modtools-modal'));
|
||||
const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid});
|
||||
|
||||
expect(modal.getAssignableAffiliations(occupant)).toEqual(['owner', 'admin', 'member', 'outcast', 'none']);
|
||||
expect(_converse.getAssignableAffiliations(occupant)).toEqual(['owner', 'admin', 'member', 'outcast', 'none']);
|
||||
|
||||
_converse.api.settings.set('modtools_disable_assign', ['owner']);
|
||||
expect(modal.getAssignableAffiliations(occupant)).toEqual(['admin', 'member', 'outcast', 'none']);
|
||||
expect(_converse.getAssignableAffiliations(occupant)).toEqual(['admin', 'member', 'outcast', 'none']);
|
||||
|
||||
_converse.api.settings.set('modtools_disable_assign', ['owner', 'admin']);
|
||||
expect(modal.getAssignableAffiliations(occupant)).toEqual(['member', 'outcast', 'none']);
|
||||
expect(_converse.getAssignableAffiliations(occupant)).toEqual(['member', 'outcast', 'none']);
|
||||
|
||||
_converse.api.settings.set('modtools_disable_assign', ['owner', 'admin', 'outcast']);
|
||||
expect(modal.getAssignableAffiliations(occupant)).toEqual(['member', 'none']);
|
||||
expect(_converse.getAssignableAffiliations(occupant)).toEqual(['member', 'none']);
|
||||
|
||||
expect(modal.getAssignableRoles(occupant)).toEqual(['moderator', 'participant', 'visitor']);
|
||||
expect(_converse.getAssignableRoles(occupant)).toEqual(['moderator', 'participant', 'visitor']);
|
||||
|
||||
_converse.api.settings.set('modtools_disable_assign', ['admin', 'moderator']);
|
||||
expect(modal.getAssignableRoles(occupant)).toEqual(['participant', 'visitor']);
|
||||
expect(_converse.getAssignableRoles(occupant)).toEqual(['participant', 'visitor']);
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
message_limit: 300,
|
||||
auto_register_muc_nickname: true,
|
||||
loglevel: 'debug',
|
||||
modtools_disable_assign: ['owner', 'moderator', 'participant', 'visitor'],
|
||||
modtools_disable_query: ['moderator', 'participant', 'visitor'],
|
||||
// modtools_disable_assign: ['owner', 'moderator', 'participant', 'visitor'],
|
||||
// modtools_disable_query: ['moderator', 'participant', 'visitor'],
|
||||
enable_smacks: true,
|
||||
// connection_options: { 'worker': '/dist/shared-connection-worker.js' },
|
||||
// persistent_store: 'IndexedDB',
|
||||
|
|
Loading…
Reference in New Issue
Block a user