Procházet zdrojové kódy

Various changes to make life for 3rd party plugin authors easier

- Add plugin utility methods to exports.
- Add a `renderItem` option to the `converse-autocomplete` component
JC Brand před 2 měsíci
rodič
revize
0ee135958a

+ 82 - 63
src/plugins/muc-views/tests/autocomplete.js

@@ -49,21 +49,26 @@ describe("The nickname autocomplete feature", function () {
         message_form.onKeyUp(at_event);
 
         await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
-        const first_child = view.querySelector('.suggestion-box__results li:first-child converse-avatar');
-        expect(first_child.textContent).toBe('D');
-        expect(first_child.nextSibling.textContent).toBe('dick');
 
-        const second_child = view.querySelector('.suggestion-box__results li:nth-child(2) converse-avatar');
-        expect(second_child.textContent).toBe('H');
-        expect(second_child.nextSibling.textContent).toBe('harry');
-
-        const third_child = view.querySelector('.suggestion-box__results li:nth-child(3) converse-avatar');
-        expect(third_child.textContent).toBe('J');
-        expect(third_child.nextSibling.textContent).toBe('jane');
-
-        const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4) converse-avatar');
-        expect(fourth_child.textContent).toBe('T');
-        expect(fourth_child.nextSibling.textContent).toBe('tom');
+        const first_child = view.querySelector('.suggestion-box__results li:first-child');
+        const first_child_avatar = first_child.querySelector('converse-avatar');
+        expect(first_child_avatar.textContent).toBe('D');
+        expect(first_child.textContent).toBe('D dick');
+
+        const second_child = view.querySelector('.suggestion-box__results li:nth-child(2)');
+        const second_child_avatar = second_child.querySelector('converse-avatar');
+        expect(second_child_avatar.textContent).toBe('H');
+        expect(second_child.textContent).toBe('H harry');
+
+        const third_child = view.querySelector('.suggestion-box__results li:nth-child(3)');
+        const third_child_avatar = third_child.querySelector('converse-avatar');
+        expect(third_child_avatar.textContent).toBe('J');
+        expect(third_child.textContent).toBe('J jane');
+
+        const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4)');
+        const fourth_child_avatar = fourth_child.querySelector('converse-avatar');
+        expect(fourth_child_avatar.textContent).toBe('T');
+        expect(fourth_child.textContent).toBe('T tom');
     }));
 
     it("shows all autocompletion options when the user presses @ right after a new line",
@@ -113,21 +118,25 @@ describe("The nickname autocomplete feature", function () {
         message_form.onKeyUp(at_event);
 
         await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
-        const first_child = view.querySelector('.suggestion-box__results li:first-child converse-avatar');
-        expect(first_child.textContent).toBe('D');
-        expect(first_child.nextSibling.textContent).toBe('dick');
-
-        const second_child = view.querySelector('.suggestion-box__results li:nth-child(2) converse-avatar');
-        expect(second_child.textContent).toBe('H');
-        expect(second_child.nextSibling.textContent).toBe('harry');
-
-        const third_child = view.querySelector('.suggestion-box__results li:nth-child(3) converse-avatar');
-        expect(third_child.textContent).toBe('J');
-        expect(third_child.nextSibling.textContent).toBe('jane');
-
-        const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4) converse-avatar');
-        expect(fourth_child.textContent).toBe('T');
-        expect(fourth_child.nextSibling.textContent).toBe('tom');
+        const first_child = view.querySelector('.suggestion-box__results li:first-child');
+        const first_child_avatar = first_child.querySelector('converse-avatar');
+        expect(first_child_avatar.textContent).toBe('D');
+        expect(first_child.textContent).toBe('D dick');
+
+        const second_child = view.querySelector('.suggestion-box__results li:nth-child(2)');
+        const second_child_avatar = second_child.querySelector('converse-avatar');
+        expect(second_child_avatar.textContent).toBe('H');
+        expect(second_child.textContent).toBe('H harry');
+
+        const third_child = view.querySelector('.suggestion-box__results li:nth-child(3)');
+        const third_child_avatar = third_child.querySelector('converse-avatar');
+        expect(third_child_avatar.textContent).toBe('J');
+        expect(third_child.textContent).toBe('J jane');
+
+        const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4)');
+        const fourth_child_avatar = fourth_child.querySelector('converse-avatar');
+        expect(fourth_child_avatar.textContent).toBe('T');
+        expect(fourth_child.textContent).toBe('T tom');
     }));
 
     it("shows all autocompletion options when the user presses @ right after an allowed character",
@@ -179,21 +188,25 @@ describe("The nickname autocomplete feature", function () {
         message_form.onKeyUp(at_event);
 
         await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
-        const first_child = view.querySelector('.suggestion-box__results li:first-child converse-avatar');
-        expect(first_child.textContent).toBe('D');
-        expect(first_child.nextSibling.textContent).toBe('dick');
-
-        const second_child = view.querySelector('.suggestion-box__results li:nth-child(2) converse-avatar');
-        expect(second_child.textContent).toBe('H');
-        expect(second_child.nextSibling.textContent).toBe('harry');
-
-        const third_child = view.querySelector('.suggestion-box__results li:nth-child(3) converse-avatar');
-        expect(third_child.textContent).toBe('J');
-        expect(third_child.nextSibling.textContent).toBe('jane');
-
-        const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4) converse-avatar');
-        expect(fourth_child.textContent).toBe('T');
-        expect(fourth_child.nextSibling.textContent).toBe('tom');
+        const first_child = view.querySelector('.suggestion-box__results li:first-child');
+        const first_child_avatar = first_child.querySelector('converse-avatar');
+        expect(first_child_avatar.textContent).toBe('D');
+        expect(first_child.textContent).toBe('D dick');
+
+        const second_child = view.querySelector('.suggestion-box__results li:nth-child(2)');
+        const second_child_avatar = second_child.querySelector('converse-avatar');
+        expect(second_child_avatar.textContent).toBe('H');
+        expect(second_child.textContent).toBe('H harry');
+
+        const third_child = view.querySelector('.suggestion-box__results li:nth-child(3)');
+        const third_child_avatar = third_child.querySelector('converse-avatar');
+        expect(third_child_avatar.textContent).toBe('J');
+        expect(third_child.textContent).toBe('J jane');
+
+        const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4)');
+        const fourth_child_avatar = fourth_child.querySelector('converse-avatar');
+        expect(fourth_child_avatar.textContent).toBe('T');
+        expect(fourth_child.textContent).toBe('T tom');
     }));
 
     it("should order by query index position and length", mock.initConverse(
@@ -232,21 +245,27 @@ describe("The nickname autocomplete feature", function () {
         message_form.onKeyUp(at_event);
         await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 3);
 
-        const first_child = view.querySelector('.suggestion-box__results li:first-child converse-avatar');
-        expect(first_child.textContent).toBe('B');
-        expect(first_child.nextElementSibling.textContent).toBe('ber');
-        expect(first_child.nextElementSibling.nextSibling.textContent).toBe('nard');
+        const first_child = view.querySelector('.suggestion-box__results li:first-child');
+        expect(first_child.textContent).toBe('B bernard');
+        const first_child_avatar = first_child.querySelector('converse-avatar');
+        expect(first_child_avatar.textContent).toBe('B');
+        const first_child_mark = first_child.querySelector('mark');
+        expect(first_child_mark.textContent).toBe('ber');
+
+        const second_child = view.querySelector('.suggestion-box__results li:nth-child(2)');
+        expect(second_child.textContent).toBe('N naber');
+        const second_child_avatar = second_child.querySelector('converse-avatar');
+        expect(second_child_avatar.textContent).toBe('N');
+        const second_child_mark = second_child.querySelector('mark');
+        expect(second_child_mark.textContent).toBe('ber');
 
-        const second_child = view.querySelector('.suggestion-box__results li:nth-child(2) converse-avatar');
-        expect(second_child.textContent).toBe('N');
-        expect(second_child.nextSibling.textContent).toBe('na');
-        expect(second_child.nextElementSibling.textContent).toBe('ber');
+        const third_child = view.querySelector('.suggestion-box__results li:nth-child(3)');
+        expect(third_child.textContent).toBe('H helberlo');
+        const third_child_avatar = third_child.querySelector('converse-avatar');
+        expect(third_child_avatar.textContent).toBe('H');
+        const third_child_mark = third_child.querySelector('mark');
+        expect(third_child_mark.textContent).toBe('ber');
 
-        const third_child = view.querySelector('.suggestion-box__results li:nth-child(3) converse-avatar');
-        expect(third_child.textContent).toBe('H');
-        expect(third_child.nextSibling.textContent).toBe('hel');
-        expect(third_child.nextSibling.nextSibling.textContent).toBe('ber');
-        expect(third_child.nextSibling.nextSibling.nextSibling.textContent).toBe('lo');
 
         // Test that when the query index is equal, results should be sorted by length
         textarea.value = '@jo';
@@ -254,8 +273,8 @@ describe("The nickname autocomplete feature", function () {
         await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 2);
 
         // First char is the avatar initial
-        expect(view.querySelector('.suggestion-box__results li:first-child').textContent).toBe('Jjohn');
-        expect(view.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('Jjones');
+        expect(view.querySelector('.suggestion-box__results li:first-child').textContent).toBe('J john');
+        expect(view.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('J jones');
     }));
 
     it("autocompletes when the user presses tab",
@@ -294,7 +313,7 @@ describe("The nickname autocomplete feature", function () {
         expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(1);
 
         // First char is the avatar initial
-        expect(view.querySelector('.suggestion-box__results li').textContent).toBe('Ssome1');
+        expect(view.querySelector('.suggestion-box__results li').textContent).toBe('S some1');
 
         const backspace_event = {
             'target': textarea,
@@ -336,8 +355,8 @@ describe("The nickname autocomplete feature", function () {
         message_form.onKeyUp(up_arrow_event);
         expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(2);
         // First char is the avatar initial
-        expect(view.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('Ssome1');
-        expect(view.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('Ssome2');
+        expect(view.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('S some1');
+        expect(view.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('S some2');
 
         message_form.onKeyDown({
             'target': textarea,
@@ -404,6 +423,6 @@ describe("The nickname autocomplete feature", function () {
         await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
         expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(1);
         // First char is the avatar initial
-        expect(view.querySelector('.suggestion-box__results li').textContent).toBe('Ssome1');
+        expect(view.querySelector('.suggestion-box__results li').textContent).toBe('S some1');
     }));
 });

+ 8 - 5
src/plugins/muc-views/utils.js

@@ -165,15 +165,18 @@ export function getAutoCompleteListItem(muc, text, input) {
     const parts = input ? text.split(regex) : [text];
     return html`
         <li aria-selected="false">
-            ${parts.map((txt) => (input && txt.match(regex) ? html`<mark>${txt}</mark>` : txt))}
             ${show_avatar
                 ? html`<converse-avatar
-                      name="${avatar_model.getDisplayName()}"
-                      height="22"
-                      width="22"
-                      class="avatar avatar-autocomplete"
+                        .model=${avatar_model}
+                        name="${avatar_model.getDisplayName()}"
+                        height="22"
+                        width="22"
+                        class="avatar avatar-autocomplete"
                   ></converse-avatar>`
                 : ''}
+            <span class="autocomplete-item">
+                ${parts.map((txt) => (input && txt.match(regex) ? html`<mark>${txt}</mark>` : txt))}
+            </span>
         </li>
     `;
 }

+ 23 - 2
src/plugins/rosterview/utils.js

@@ -63,12 +63,12 @@ export async function declineContactRequest(contact) {
             api.blocklist.add(contact.get('jid'));
             api.toast.show('declined-and-blocked', {
                 type: 'success',
-                body: __('Contact request declined and user blocked')
+                body: __('Contact request declined and user blocked'),
             });
         } else {
             api.toast.show('request-declined', {
                 type: 'success',
-                body: __('Contact request declined')
+                body: __('Contact request declined'),
             });
         }
         contact.destroy();
@@ -371,3 +371,24 @@ export async function getNamesAutoCompleteList(query) {
         ...i, // Return rest of the JSON as well, could be useful to 3rd party plugin
     }));
 }
+
+Object.assign(u, {
+    rosterview: {
+        removeContact,
+        declineContactRequest,
+        blockContact,
+        unblockContact,
+        highlightRosterItem,
+        toggleGroup,
+        getFilterCriteria,
+        isContactFiltered,
+        shouldShowContact,
+        shouldShowGroup,
+        populateContactsMap,
+        contactsComparator,
+        groupsComparator,
+        getGroupsAutoCompleteList,
+        getJIDsAutoCompleteList,
+        getNamesAutoCompleteList,
+    },
+});

+ 8 - 1
src/shared/autocomplete/component.js

@@ -1,6 +1,6 @@
 import AutoComplete from "./autocomplete.js";
 import { CustomElement } from "shared/components/element.js";
-import { FILTER_CONTAINS, FILTER_STARTSWITH } from "./utils.js";
+import { FILTER_CONTAINS, FILTER_STARTSWITH, getAutoCompleteItem } from "./utils.js";
 import { api, u } from "@converse/headless";
 import { html } from "lit";
 
@@ -32,6 +32,9 @@ import { html } from "lit";
  *  The `name` attribute of the `input` element
  * @property {String} [placeholder]
  *  The `placeholder` attribute of the `input` element
+ * @property {Function} [renderItem]
+ *  Optional function which must return a lit TemplateResult which renders an
+ *  suggestion item in the autocomplete list.
  * @property {String} [triggers]
  *  String of space separated characters which trigger autocomplete
  * @property {Function} [validate]
@@ -61,6 +64,7 @@ export default class AutoCompleteComponent extends CustomElement {
             name: { type: String },
             placeholder: { type: String },
             position: { type: String },
+            renderItem: { type: Function },
             required: { type: Boolean },
             triggers: { type: String },
             validate: { type: Function },
@@ -84,6 +88,8 @@ export default class AutoCompleteComponent extends CustomElement {
         this.name = "";
         this.placeholder = "";
         this.position = "above";
+        this.renderItem = getAutoCompleteItem;
+
         this.required = false;
         this.triggers = "";
         this.validate = null;
@@ -139,6 +145,7 @@ export default class AutoCompleteComponent extends CustomElement {
             match_current_word: true,
             max_items: this.max_items,
             min_chars: this.min_chars,
+            item: this.renderItem,
         });
         this.auto_complete.on("suggestion-box-selectcomplete", ({ suggestion }) => {
             this.auto_completing = false;

+ 2 - 7
src/shared/autocomplete/styles/_autocomplete.scss

@@ -40,7 +40,7 @@
                 z-index: -1;
             }
             border-radius: .3em;
-            border: 0.15em solid var(--muc-color);
+            border: 0.15em solid var(--secondary-color);
             box-shadow: .05em .2em .6em rgba(0,0,0,.1);
             box-sizing: border-box;
             left: 0;
@@ -126,14 +126,9 @@
     }
 
     .suggestion-box li:hover mark {
-        background: var(--secondary-color);
+        background: var(--secondary-color-hover);
         color: var(--link-color);
     }
-
-    .suggestion-box li[aria-selected="true"] mark {
-        background: var(--muc-color);
-        color: inherit;
-    }
 }
 
 .conversejs.converse-fullscreen {

+ 8 - 0
src/shared/autocomplete/utils.js

@@ -105,3 +105,11 @@ export function getAutoCompleteItem(text, input) {
         </li>
     `;
 }
+
+
+Object.assign(u, {
+    autocomplete: {
+        regExpEscape,
+        getAutoCompleteItem,
+    },
+});

+ 9 - 2
src/shared/styles/alerts.scss

@@ -1,6 +1,14 @@
 .conversejs {
+    .modal-alert {
+        .alert {
+            p {
+                margin: 0;
+            }
+        }
+    }
 
-    .alert-info, .alert-danger {
+    .alert-info,
+    .alert-danger {
         h3 {
             color: var(--background-color);
             font-size: large;
@@ -30,5 +38,4 @@
         border-color: var(--danger-color-dark);
         background-color: var(--danger-color);
     }
-
 }

+ 1 - 1
src/shared/styles/themes/classic.scss

@@ -51,7 +51,7 @@
 
     --converse-body-bg: var(--background-color);
     --converse-body-color: var(--foreground-color) !important;
-    --converse-highlight-color: var(--red);
+    --converse-highlight-color: var(--blue);
     .navbar-nav {
         --converse-nav-link-color: var(--link-color) !important;
     }

+ 8 - 0
src/types/shared/autocomplete/component.d.ts

@@ -26,6 +26,9 @@
  *  The `name` attribute of the `input` element
  * @property {String} [placeholder]
  *  The `placeholder` attribute of the `input` element
+ * @property {Function} [renderItem]
+ *  Optional function which must return a lit TemplateResult which renders an
+ *  suggestion item in the autocomplete list.
  * @property {String} [triggers]
  *  String of space separated characters which trigger autocomplete
  * @property {Function} [validate]
@@ -80,6 +83,9 @@ export default class AutoCompleteComponent extends CustomElement {
         position: {
             type: StringConstructor;
         };
+        renderItem: {
+            type: FunctionConstructor;
+        };
         required: {
             type: BooleanConstructor;
         };
@@ -107,6 +113,7 @@ export default class AutoCompleteComponent extends CustomElement {
     name: string;
     placeholder: string;
     position: string;
+    renderItem: typeof getAutoCompleteItem;
     required: boolean;
     triggers: string;
     validate: any;
@@ -121,5 +128,6 @@ export default class AutoCompleteComponent extends CustomElement {
     onChange(): Promise<this>;
 }
 import { CustomElement } from "shared/components/element.js";
+import { getAutoCompleteItem } from "./utils.js";
 import AutoComplete from "./autocomplete.js";
 //# sourceMappingURL=component.d.ts.map