Browse Source

Allow multiple groups to be set

JC Brand 4 tháng trước cách đây
mục cha
commit
a0b3acd991

+ 1 - 1
CHANGES.md

@@ -68,7 +68,7 @@
 - Change contacts filter to rename the anachronistic `Online` state to `Available`.
 - Enable [reuse_scram_keys](https://conversejs.org/docs/html/configuration.html#reuse-scram-keys) by default.
 - New `loadEmojis` hook, to customize emojis at runtime.
-- Add new themes 'Cyberpunk' and 'Nord' and remove the old 'Concord' theme.
+- Add new themes 'Cyberpunk' and 'Nordic' and remove the old 'Concord' theme.
 - Improved accessibility.
 - New "getOccupantActionButtons" hook, so that plugins can add actions on MUC occupants.
 - MUC occupants badges: displays short labels, with full label as title.

+ 3 - 3
src/plugins/rosterview/modals/accept-contact-request.js

@@ -42,12 +42,12 @@ export default class AcceptContactRequest extends BaseModal {
         const form = /** @type {HTMLFormElement} */ (ev.target);
         const data = new FormData(form);
         const name = /** @type {string} */ (data.get("name") || "").trim();
-        const group = data.get('group');
+        const groups = /** @type {string} */(data.get('groups'))?.split(',').map((g) => g.trim()) || [];
         try {
             await _converse.state.roster.sendContactAddIQ({
                 jid: this.contact.get("jid"),
                 name,
-                group,
+                groups,
             });
             this.contact.authorize().subscribe();
         } catch (e) {
@@ -57,7 +57,7 @@ export default class AcceptContactRequest extends BaseModal {
         }
         this.contact.save({
             nickname: name,
-            groups: [group]
+            groups,
         });
         this.modal.hide();
     }

+ 5 - 4
src/plugins/rosterview/modals/add-contact.js

@@ -45,11 +45,11 @@ export default class AddContactModal extends BaseModal {
      * @param {HTMLFormElement} _form
      * @param {string} jid
      * @param {string} name
-     * @param {FormDataEntryValue} group
+     * @param {string[]} groups
      */
-    async afterSubmission (_form, jid, name, group) {
+    async afterSubmission (_form, jid, name, groups) {
         try {
-            await api.contacts.add({ jid, name, groups: Array.isArray(group) ? group : [group] });
+            await api.contacts.add({ jid, name, groups });
         } catch (e) {
             log.error(e);
             this.model.set('error', __('Sorry, something went wrong'));
@@ -81,7 +81,8 @@ export default class AddContactModal extends BaseModal {
         }
 
         if (this.validateSubmission(jid)) {
-            this.afterSubmission(form, jid, name, data.get('group'));
+            const groups = /** @type {string} */(data.get('groups'))?.split(',').map((g) => g.trim()) || [];
+            this.afterSubmission(form, jid, name, groups);
         }
     }
 }

+ 7 - 3
src/plugins/rosterview/modals/templates/accept-contact-request.js

@@ -7,7 +7,8 @@ import { html } from "lit";
  */
 export default (el) => {
     const i18n_add = __("Add");
-    const i18n_group = __("Group");
+    const i18n_groups = __("Groups");
+    const i18n_groups_help = __("Use commas to separate multiple values");
     const i18n_nickname = __("Name");
     const error = el.model.get("error");
 
@@ -19,8 +20,11 @@ export default (el) => {
                 <input type="text" name="name" value="${el.contact.vcard?.get('fullname') || ''}" class="form-control" />
             </div>
             <div class="mb-3">
-                <label class="form-label clearfix" for="name">${i18n_group}:</label>
-                <converse-autocomplete .list=${getGroupsAutoCompleteList()} name="group"></converse-autocomplete>
+                <label class="form-label clearfix" for="name">${i18n_groups}:</label>
+                <div class="mb-1">
+                    <small class="form-text text-muted">${i18n_groups_help}</small>
+                </div>
+                <converse-autocomplete .list=${getGroupsAutoCompleteList()} name="groups"></converse-autocomplete>
             </div>
             <button type="submit" class="btn btn-primary">${i18n_add}</button>
         </form>

+ 7 - 3
src/plugins/rosterview/modals/templates/add-contact.js

@@ -15,7 +15,8 @@ import { FILTER_STARTSWITH } from 'shared/autocomplete/utils';
 export default (el) => {
     const i18n_add = __('Add');
     const i18n_contact_placeholder = __('name@example.org');
-    const i18n_group = __('Group');
+    const i18n_groups = __('Groups');
+    const i18n_groups_help = __("Use commas to separate multiple values");
     const i18n_nickname = __('Name');
     const i18n_xmpp_address = __('XMPP Address');
     const error = el.model.get('error');
@@ -60,10 +61,13 @@ export default (el) => {
                     }
                 </div>
                 <div class="mb-3">
-                    <label class="form-label clearfix" for="name">${i18n_group}:</label>
+                    <label class="form-label clearfix" for="name">${i18n_groups}:</label>
+                    <div class="mb-1">
+                        <small class="form-text text-muted">${i18n_groups_help}</small>
+                    </div>
                     <converse-autocomplete
                         .list=${getGroupsAutoCompleteList()}
-                        name="group"></converse-autocomplete>
+                        name="groups"></converse-autocomplete>
                 </div>
                 <button type="submit" class="btn btn-primary">${i18n_add}</button>
             </form>

+ 14 - 4
src/plugins/rosterview/tests/add-contact-modal.js

@@ -5,6 +5,8 @@ const sizzle = converse.env.sizzle;
 
 describe("The 'Add Contact' widget", function () {
 
+    beforeEach(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza }));
+
     it("opens up an add modal when you click on it",
             mock.initConverse([], {}, async function (_converse) {
 
@@ -26,6 +28,9 @@ describe("The 'Add Contact' widget", function () {
         const input_name = modal.querySelector('input[name="name"]');
         input_jid.value = 'someone@';
 
+        const groups_input = modal.querySelector('input[name="groups"]');
+        groups_input.value = 'Friends, Countrymen';
+
         const evt = new Event('input');
         input_jid.dispatchEvent(evt);
         expect(modal.querySelector('.suggestion-box li').textContent).toBe('someone@montague.lit');
@@ -35,10 +40,15 @@ describe("The 'Add Contact' widget", function () {
 
         const sent_IQs = _converse.api.connection.get().IQ_stanzas;
         const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
-        expect(Strophe.serialize(sent_stanza)).toEqual(
-            `<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
-                `<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit" name="Someone"><group></group></item></query>`+
-            `</iq>`);
+        expect(sent_stanza).toEqualStanza(stx`
+            <iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">
+                <query xmlns="jabber:iq:roster">
+                    <item jid="someone@montague.lit" name="Someone">
+                        <group>Friends</group>
+                        <group>Countrymen</group>
+                    </item>
+                </query>+
+            </iq>`);
     }));
 
     it("can be configured to not provide search suggestions",

+ 1 - 1
src/plugins/rosterview/tests/protocol.js

@@ -63,7 +63,7 @@ describe("Presence subscriptions", function () {
             const form = modal.querySelector('form.add-xmpp-contact');
             form.querySelector('input[name="jid"]').value = 'contact@example.org';
             form.querySelector('input[name="name"]').value = 'Chris Contact';
-            form.querySelector('input[name="group"]').value = 'My Buddies';
+            form.querySelector('input[name="groups"]').value = 'My Buddies';
             form.querySelector('[type="submit"]').click();
 
             /* In preparation for being able to render the contact in the

+ 7 - 4
src/plugins/rosterview/tests/roster.js

@@ -1214,8 +1214,8 @@ describe("The Contacts Roster", function () {
             await u.waitUntil(() => u.isVisible(modal), 1000);
 
             expect(modal.querySelector('input[name="name"]')?.value).toBe('Escalus, prince of Verona');
-            const group_input = modal.querySelector('input[name="group"]');
-            group_input.value = 'Princes';
+            const groups_input = modal.querySelector('input[name="groups"]');
+            groups_input.value = 'Princes, Veronese';
 
             const sent_stanzas = _converse.api.connection.get().sent_stanzas;
             while (sent_stanzas.length) sent_stanzas.pop();
@@ -1226,7 +1226,10 @@ describe("The Contacts Roster", function () {
             expect(stanza).toEqualStanza(
                 stx`<iq type="set" xmlns="jabber:client" id="${stanza.getAttribute('id')}">
                         <query xmlns="jabber:iq:roster">
-                            <item jid="${contact.get('jid')}" name="Escalus, prince of Verona"/>
+                            <item jid="${contact.get('jid')}" name="Escalus, prince of Verona">
+                                <group>Princes</group>
+                                <group>Veronese</group>
+                            </item>
                         </query>
                     </iq>`);
 
@@ -1240,7 +1243,7 @@ describe("The Contacts Roster", function () {
 
             await u.waitUntil(() => contact.authorize.calls.count());
             expect(contact.authorize).toHaveBeenCalled();
-            expect(contact.get('groups')).toEqual(['Princes']);
+            expect(contact.get('groups')).toEqual(['Princes', 'Veronese']);
         }));
 
         it("can have their requests denied by the user",

+ 1 - 1
src/shared/modals/user-details.js

@@ -82,7 +82,7 @@ export default class UserDetailsModal extends BaseModal {
         const form = /** @type {HTMLFormElement} */ (ev.target);
         const data = new FormData(form);
         const name = /** @type {string} */ (data.get("name") || "").trim();
-        const groups = /** @type {string} */(data.get('groups'))?.split(',') || [];
+        const groups = /** @type {string} */(data.get('groups'))?.split(',').map((g) => g.trim()) || [];
         this.model.contact.save({
             nickname: name,
             groups,

+ 1 - 2
src/shared/tests/mock.js

@@ -760,8 +760,7 @@ function getMockVcardFetcher (settings) {
     }
 }
 
-//const theme = ['dracula', 'classic', 'cyberpunk', 'nordic'][Math.floor(Math.random()*4)];
-const theme = ['nordic'];
+const theme = ['dracula', 'classic', 'cyberpunk', 'nordic'][Math.floor(Math.random()*4)];
 
 async function _initConverse (settings) {
     clearStores();

+ 2 - 2
src/types/plugins/rosterview/modals/add-contact.d.ts

@@ -9,9 +9,9 @@ export default class AddContactModal extends BaseModal {
      * @param {HTMLFormElement} _form
      * @param {string} jid
      * @param {string} name
-     * @param {FormDataEntryValue} group
+     * @param {string[]} groups
      */
-    afterSubmission(_form: HTMLFormElement, jid: string, name: string, group: FormDataEntryValue): Promise<void>;
+    afterSubmission(_form: HTMLFormElement, jid: string, name: string, groups: string[]): Promise<void>;
     /**
      * @param {Event} ev
      */