Browse Source

Add the ability to filter the results in the modtools modal

JC Brand 5 năm trước cách đây
mục cha
commit
00cac6d250
5 tập tin đã thay đổi với 210 bổ sung16 xóa
  1. 7 7
      package-lock.json
  2. 1 0
      sass/_modal.scss
  3. 164 0
      spec/modtools.js
  4. 19 2
      src/converse-muc-views.js
  5. 19 7
      src/templates/moderator_tools_modal.js

+ 7 - 7
package-lock.json

@@ -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": {

+ 1 - 0
sass/_modal.scss

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

+ 164 - 0
spec/modtools.js

@@ -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'], {},

+ 19 - 2
src/converse-muc-views.js

@@ -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();

+ 19 - 7
src/templates/moderator_tools_modal.js

@@ -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>