Add the ability to filter the results in the modtools modal

This commit is contained in:
JC Brand 2020-03-23 13:14:18 +01:00
parent b215c59bd0
commit 00cac6d250
5 changed files with 210 additions and 16 deletions

14
package-lock.json generated
View File

@ -14180,7 +14180,7 @@
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true,
"requires": {
@ -14190,7 +14190,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -14232,7 +14232,7 @@
},
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@ -14251,7 +14251,7 @@
},
"meow": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"dev": true,
"requires": {
@ -18737,7 +18737,7 @@
},
"pinkie-promise": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
"resolved": "http://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
"dev": true,
"requires": {
@ -20228,7 +20228,7 @@
},
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@ -20241,7 +20241,7 @@
},
"os-locale": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"dev": true,
"requires": {

View File

@ -3,6 +3,7 @@
#converse-modals {
.modal {
background-color: rgba(0, 0, 0, 0.4);
.modal-body {
overflow-y: auto;
max-height: 75vh;

View File

@ -3,6 +3,7 @@
} (this, function (jasmine, mock, test_utils) {
const _ = converse.env._;
const $iq = converse.env.$iq;
const $pres = converse.env.$pres;
const sizzle = converse.env.sizzle;
const Strophe = converse.env.Strophe;
const u = converse.env.utils;
@ -137,6 +138,169 @@
done();
}));
it("allows you to filter affiliation search results",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
const members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'member'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'member'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'member'},
{'jid': 'juliet@capulet.lit', 'nick': 'juliet', 'affiliation': 'member'},
];
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 6), 1000);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
const select = modal.el.querySelector('.select-affiliation');
expect(select.value).toBe('owner');
select.value = 'member';
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
const user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(6);
const filter = modal.el.querySelector('[name="filter"]');
expect(filter).not.toBe(null);
filter.value = 'romeo';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
filter.value = 'r';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 3));
filter.value = 'gower';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
done();
}));
it("allows you to filter role search results",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', []);
const view = _converse.chatboxviews.get(muc_jid);
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/nomorenicks`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `nomorenicks@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/newb`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `newb@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/some1`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `some1@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/oldhag`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `oldhag@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/crone`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `crone@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/tux`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `tux@montague.lit`,
'role': 'participant'
})
));
await u.waitUntil(() => (view.model.occupants.length === 7), 1000);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const tab = modal.el.querySelector('#roles-tab');
tab.click();
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
const select = modal.el.querySelector('.select-role');
expect(select.value).toBe('moderator');
select.value = 'participant';
const button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_role);
const user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(6);
const filter = modal.el.querySelector('[name="filter"]');
expect(filter).not.toBe(null);
filter.value = 'tux';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
filter.value = 'r';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 2));
filter.value = 'crone';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
done();
}));
it("shows an error message if a particular affiliation list may not be retrieved",
mock.initConverse(
['rosterGroupsFetched'], {},

View File

@ -230,6 +230,9 @@ converse.plugins.add('converse-muc-views', {
this.chatroomview = attrs.chatroomview;
BootstrapModal.prototype.initialize.apply(this, arguments);
this.affiliations_filter = '';
this.roles_filter = '';
this.listenTo(this.model, 'change:role', () => {
this.users_with_role = this.getUsersWithRole();
this.render();
@ -252,15 +255,19 @@ converse.plugins.add('converse-muc-views', {
toHTML () {
const occupant = this.chatroomview.model.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)),
'assignable_affiliations': this.getAssignableAffiliations(occupant),
'assignable_roles': this.getAssignableRoles(occupant),
'roles_filter': this.roles_filter,
'switchTab': ev => this.switchTab(ev),
'toggleForm': ev => this.toggleForm(ev),
'users_with_affiliation': this.users_with_affiliation,
@ -342,6 +349,16 @@ converse.plugins.add('converse-muc-views', {
});
},
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();

View File

@ -13,6 +13,7 @@ const i18n_new_role = __('New Role');
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_reason = __('Reason');
const i18n_filter = __('Type here to filter the search results');
const i18n_role = __('Role');
const i18n_show_users = __('Show users');
@ -163,7 +164,6 @@ const tpl_navigation = (o) => html`
export default (o) => {
const show_both_tabs = o.queryable_roles.length && o.queryable_affiliations.length;
return html`
<div class="modal-dialog" role="document">
@ -177,7 +177,6 @@ export default (o) => {
${ 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}>
@ -197,17 +196,25 @@ export default (o) => {
</div>
</div>
<div class="row">
<div class="col pt-2"><p class="helptext pb-3">${getAffiliationHelpText(o.affiliation)}</p></div>
<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>` : '' }
${ (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 => affiliation_list_item(Object.assign({item}, o))) }
(o.users_with_affiliation || []).map(item => (item.nick.match(o.affiliations_filter) ? affiliation_list_item(Object.assign({item}, o)) : '')) }
</ul>
</div>
</div>
@ -228,15 +235,20 @@ export default (o) => {
</div>
</div>
<div class="row">
<div class="col pt-2"><p class="helptext pb-3">${getRoleHelpText(o.role)}</p></div>
<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 => role_list_item(Object.assign({item}, o))) }
${ (o.users_with_role || []).map(item => (item.nick.match(o.roles_filter) ? role_list_item(Object.assign({item}, o)) : '')) }
</ul>
</div>
</div>